Files
framework_note/devop_note/Uvicorn and Gunicorn.md
T
2026-05-30 16:02:33 -04:00

5.5 KiB
Raw Blame History

aliases, up, tags
aliases up tags
00_devop_note
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

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 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

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:

  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:

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).

  • uWSGI — older alternative, mostly WSGI
  • Apache Tomcat — the Java equivalent (servlet container)