Gunicorn & Uvicorn
Gunicorn manages worker processes for WSGI and ASGI apps; Uvicorn implements the ASGI server. Production FastAPI on 0.115+ commonly uses Gunicorn with UvicornWorker for multi-process concurrency plus async I/O.
Recipe
gunicorn app.main:app \
-w 4 \
-k uvicorn.workers.UvicornWorker \
-b 0.0.0.0:8000 \
--timeout 60 \
--graceful-timeout 30 \
--access-logfile -When to reach for this:
- Production FastAPI/Starlette deployments
- Flask/Django WSGI with sync workers (
-k syncdefault) - Container single-process model with internal worker pool
- Graceful deploys needing SIGTERM handling
Working Example
Tuned Gunicorn config file for FastAPI with logging and worker count from env.
# gunicorn.conf.py
import multiprocessing
import os
bind = f"0.0.0.0:{os.environ.get('PORT', '8000')}"
worker_class = "uvicorn.workers.UvicornWorker"
workers = int(os.environ.get("WEB_CONCURRENCY", multiprocessing.cpu_count()))
timeout = 60
graceful_timeout = 30
keepalive = 5
accesslog = "-"
errorlog = "-"
loglevel = os.environ.get("LOG_LEVEL", "info")gunicorn -c gunicorn.conf.py app.main:appWhat this demonstrates:
WEB_CONCURRENCYpattern matches Heroku/Render conventionsUvicornWorkerruns ASGI per worker process- Access/error logs to stdout for container log collectors
Deep Dive
Worker Models
| Stack | Worker class |
|---|---|
| Flask/Django sync | sync (default) |
| FastAPI async | uvicorn.workers.UvicornWorker |
| gevent (legacy) | gevent |
Tuning Starting Points
- Workers:
2-4 x CPU coresfor sync CPU-bound; fewer for memory-heavy ML - Timeout: above p99 request time; avoid killing slow legitimate requests
- Keepalive: match reverse proxy keepalive settings
Python Notes
# Dev only - single worker reload
uvicorn app.main:app --reload --host 127.0.0.1 --port 8000Gotchas
- Too many workers on ML app - OOM kills container. Fix: reduce workers or externalize inference.
- Sync workers for purely async I/O app - underutilizes concurrency. Fix: UvicornWorker or pure uvicorn with async.
- Timeout shorter than DB query - 502 during reports. Fix: raise timeout or move long jobs to queue.
- No graceful timeout on deploy - in-flight requests aborted. Fix:
--graceful-timeout+ preStop hook delay in K8s. - Running uvicorn --reload in prod - insecure and unstable. Fix: gunicorn config without reload.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Hypercorn | HTTP/2 ASGI needs | Team standard is Gunicorn |
| uWSGI | Legacy Django shops | Greenfield FastAPI |
| Multiple uvicorn replicas | K8s one-process-per-pod model | Want in-container worker pool |
FAQs
gunicorn vs uvicorn alone?
Gunicorn supervises multiple Uvicorn workers; single uvicorn fine for dev and tiny prod with external scaling.
How do I calculate workers?
Start at CPU count for I/O-bound async; half CPU for CPU-bound sync; measure latency and RSS under load.
Does Django use UvicornWorker?
Django 5.2 async views can; most Django deploys remain sync Gunicorn workers unless async path is explicit.
What about websockets?
UvicornWorker supports websockets; sync Gunicorn workers do not.
How do I expose metrics?
Prometheus multiprocess dir when using multiple sync workers; ASGI middleware for request metrics.
How do workers reload code?
Deploy new container image - do not rely on HUP reload in immutable infra.
Thread workers?
gthread for some WSGI I/O overlap - less common than async ASGI for new FastAPI services.
How do I bind IPv6?
[::]:8000 when platform requires dual-stack - verify load balancer config.
SSL termination?
At load balancer or ingress - not usually on Gunicorn inside container.
How does this interact with Kubernetes?
One container runs Gunicorn with N workers; HPA scales pods on CPU/RPS - see Kubernetes page.
Related
- Deployment Basics - WSGI/ASGI intro
- Dockerizing Python - CMD uses Gunicorn
- Zero-Downtime Deploys - graceful shutdown
- Health & Readiness - probes
- Performance Monitoring (APM) - latency tuning
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.