--- aliases: up: "[[00_devop_note]]" tags: - devop --- # 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 ```bash gunicorn myproject.wsgi:application --workers 4 --bind 0.0.0.0:8000 ``` Or with a config file `gunicorn.conf.py`: ```python 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 worker - `gthread` — 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 ```bash uvicorn myproject.main:app --host 0.0.0.0 --port 8000 ``` For development with auto-reload: ```bash 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: 1. Run multiple Uvicorn instances behind a load balancer, OR 2. Run Uvicorn **inside Gunicorn** as worker processes (the common pattern) --- ## The common production combo: Gunicorn + Uvicorn This is the standard FastAPI production setup: ```bash 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 1. **Don't run Uvicorn `--reload` in production** — it's a dev-only feature, has overhead and isn't safe. 2. **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. 3. **Timeouts matter** — Gunicorn's default `timeout=30s` will kill workers running long async tasks. Tune it for your workload. 4. **Nginx is still recommended in front** — Uvicorn/Gunicorn don't do SSL termination, static files, or rate limiting as well as Nginx. 5. **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)