Gunicorn Production Checklist for Django
A production checklist for running Django on Gunicorn: bind address and socket, worker class and count, timeouts and graceful shutdown, worker recycling, logging, forwarded headers, process supervision, and the proxy in front.
Problem statement
Gunicorn runs most production Django sites, and its defaults are deliberately simple: one sync worker, a 30-second timeout, logs to stderr. Real deployments need deliberate choices for workers, timeouts, recycling, logging, and supervision, and a reverse proxy in front. This checklist collects those choices in one place so a new deployment or a review of an existing one can confirm nothing important is left at a default that does not fit.
- Bind to a Unix socket or
127.0.0.1behind Nginx or Caddy (or0.0.0.0:$PORTin containers behind a load balancer). - Choose workers by CPU and memory,
gthreadfor some I/O overlap, and measure per-worker memory. - Set
timeout,graceful_timeout, andkeepaliveexplicitly, consistent with proxy timeouts. - Recycle workers with
max_requestsplus jitter. - Log access and errors to stdout/stderr with request duration.
- Supervise with systemd (or the container runtime) and reload gracefully on deploy.
Checklist
Configuration and binding
- Configuration lives in a committed
gunicorn.conf.py, not a long command line. - Gunicorn listens on a socket or loopback address; only the proxy is public.
forwarded_allow_ipslists only your proxy addresses.- The process runs as an unprivileged user, never root.
Workers
- Worker count fits memory: workers × peak RSS plus other services stays under RAM.
- Worker class matches the app: sync or
gthreadfor typical Django, Uvicorn workers for ASGI, gevent only with care. max_requests(for example 1000) andmax_requests_jitter(100) are set.preload_appis a deliberate choice with post-fork connection handling if enabled.
Timeouts and shutdown
timeoutis above normal request time and below the proxy's read timeout.graceful_timeoutcovers in-flight requests during reloads; systemdTimeoutStopSecor container grace period is longer.keepaliveis only relevant for non-sync workers behind a proxy that reuses connections.- Slow work (exports, imports, reports) runs in Celery, not in requests.
Logging and observability
accesslog = "-"anderrorlog = "-"with a format including request duration.- Application logging goes to stdout through Django's
LOGGING. - Worker timeouts and OOM kills are alerted on (search for
WORKER TIMEOUTand kernel OOM messages).
Supervision and deploys
- systemd unit with
Restart=on-failure,RuntimeDirectoryfor the socket, and anEnvironmentFile; or a container with a restart policy. - Deploys use
systemctl reload(HUP) for code changes, or a full restart with preloading. - Nginx or Caddy in front handles TLS, static files, buffering, and body size limits.
Explanation
Gunicorn's job is narrow: run a pool of Django processes, give each request to a worker, and keep the pool healthy. Most production incidents involving it come from mismatched sizing (too many workers for memory), mismatched timeouts across layers, unbounded memory growth, or missing supervision. Making each of those an explicit, reviewed setting turns Gunicorn into a predictable component, with the proxy handling the parts it is not designed for.
Edge cases / notes
- Containers: set
GUNICORN_WORKERSexplicitly; CPU counts inside containers can reflect the host. - PaaS: platforms set
PORT; bind to it and keep the rest of the config the same. - ASGI: Uvicorn workers under Gunicorn follow the same checklist, with WebSocket timeouts at the proxy.
- Windows: Gunicorn does not run on Windows; use Waitress.
Next steps
- Write the config with Write a gunicorn.conf.py for Django.
- Size workers with How to Optimize Gunicorn Workers for Django.
FAQ
How many Gunicorn workers should I run?
Start near 2-4 per CPU core, then limit by memory: workers times peak RSS must fit in RAM with other services.
What timeout should Gunicorn use?
Above your normal request time, typically 30-60 seconds, and below the proxy's read timeout.
Should Gunicorn face the internet directly?
No. Put Nginx, Caddy, or a load balancer in front for TLS, static files, and buffering.
Why use max_requests?
To recycle workers periodically and contain memory growth from leaks or large requests.