5.5 KiB
aliases, up, tags
| aliases | up | tags | |
|---|---|---|---|
| 00_devop_note |
|
Uvicorn and Gunicorn
Both are Python application servers — they run your Python web app and hand requests back and forth with a web server like Nginx. But they solve different problems, and you'll often see them used together.
The key split: Gunicorn is WSGI (synchronous), Uvicorn is ASGI (asynchronous).
Quick refresher: WSGI vs ASGI
| WSGI | ASGI | |
|---|---|---|
| Spec | PEP 3333 | "Asynchronous Server Gateway Interface" |
| Model | Sync, one request per worker at a time | Async, many concurrent requests per worker |
| Frameworks | Django (classic), Flask | FastAPI, Starlette, Django (async views), Sanic |
| Supports WebSockets? | No | Yes |
| Supports HTTP/2, SSE? | No | Yes |
If your app uses async def view functions or WebSockets, you need ASGI.
If it's a traditional Django/Flask app with def views, WSGI is fine.
Gunicorn ("Green Unicorn")
A WSGI server. Mature, simple, battle-tested. The de facto default for Django/Flask in production.
Why people pick it
- Simple config — most options are sensible by default
- Pre-fork worker model — master process forks N workers; each worker handles one request at a time
- Stable — has been the standard for years
- Good signal handling — graceful reloads, zero-downtime restarts
Minimal usage
gunicorn myproject.wsgi:application --workers 4 --bind 0.0.0.0:8000
Or with a config file gunicorn.conf.py:
bind = "unix:/tmp/myproject.sock"
workers = 4
worker_class = "sync" # default
timeout = 30
accesslog = "-"
errorlog = "-"
Worker classes
Gunicorn lets you swap the worker type:
sync— default, one request at a time per workergthread— threaded workers (good for I/O-bound apps)gevent/eventlet— async via greenlets (legacy)uvicorn.workers.UvicornWorker— this is the bridge to ASGI (see below)
How many workers?
Rule of thumb: (2 × CPU cores) + 1. So a 4-core box → 9 workers.
Uvicorn
An ASGI server built on uvloop and httptools — both written in C, which makes it very fast. It's the standard server for FastAPI and modern async Python web apps.
Why people pick it
- Async-native — handles thousands of concurrent connections per worker
- WebSockets + HTTP/2 support
- Very fast — uvloop is a drop-in faster replacement for asyncio's event loop
- Lightweight — small dependency surface
Minimal usage
uvicorn myproject.main:app --host 0.0.0.0 --port 8000
For development with auto-reload:
uvicorn myproject.main:app --reload
Where it falls short alone
Uvicorn by itself is single-process. To use multiple CPU cores in production, you need to either:
- Run multiple Uvicorn instances behind a load balancer, OR
- Run Uvicorn inside Gunicorn as worker processes (the common pattern)
The common production combo: Gunicorn + Uvicorn
This is the standard FastAPI production setup:
gunicorn myproject.main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000
What's happening:
- Gunicorn is the process manager — forks workers, handles signals, restarts dead workers, manages graceful shutdowns
- Uvicorn runs inside each Gunicorn worker — provides the ASGI event loop that runs your async code
You get the best of both: Gunicorn's robust process management + Uvicorn's async performance.
Visual
┌─ Uvicorn worker (async loop) ─ FastAPI app
│
Nginx → Gunicorn ├─ Uvicorn worker (async loop) ─ FastAPI app
(master) │
├─ Uvicorn worker (async loop) ─ FastAPI app
│
└─ Uvicorn worker (async loop) ─ FastAPI app
Decision guide
| Your situation | Use |
|---|---|
| Django (sync), Flask | Gunicorn alone |
| FastAPI, Starlette, async Django | Gunicorn + UvicornWorker |
| Local dev, single FastAPI process | Uvicorn alone (--reload) |
| WebSockets required | Must be ASGI → Uvicorn (alone or under Gunicorn) |
| Legacy app, uWSGI already configured | uWSGI — no urgent need to migrate |
Comparison with uWSGI
| Gunicorn | Uvicorn | uWSGI | |
|---|---|---|---|
| Protocol | WSGI | ASGI | WSGI (+ many others) |
| Async support | No (sync workers) | Yes (native) | Limited |
| Config complexity | Low | Low | Very high |
| WebSockets | No | Yes | Partial |
| Speed (raw) | Good | Fastest for async | Fast but heavy |
| Maintained actively | Yes | Yes | Concerns |
| Best for | Django/Flask | FastAPI | Legacy / specialized features |
Common gotchas
- Don't run Uvicorn
--reloadin production — it's a dev-only feature, has overhead and isn't safe. - Workers ≠ threads — each Gunicorn worker is a separate Python process with its own memory. Database connections, in-memory caches, etc. are not shared between workers.
- Timeouts matter — Gunicorn's default
timeout=30swill kill workers running long async tasks. Tune it for your workload. - Nginx is still recommended in front — Uvicorn/Gunicorn don't do SSL termination, static files, or rate limiting as well as Nginx.
- Logging — by default both log to stdout/stderr; route to your log aggregator via container stdout (
-in config).
Related
- uWSGI — older alternative, mostly WSGI
- Apache Tomcat — the Java equivalent (servlet container)