Gunicorn WORKER TIMEOUT and 502 on Django: How to Fix
Your Django app returns 502 Bad Gateway and gunicorn logs [CRITICAL] WORKER TIMEOUT? Find the slow request, match proxy and gunicorn timeouts, and size workers correctly.
A gunicorn [CRITICAL] WORKER TIMEOUT followed by a 502 means a worker was busy for longer than the --timeout value (30 seconds by default), so gunicorn killed it and the proxy in front returned an error. Raising the timeout only hides the problem. Find the slow request, move long work into a background task, and make sure your proxy timeout is longer than the gunicorn one.
This error often appears right after a deploy, but the deploy is usually only the trigger. A new query, a bigger table, a heavy import at startup or a slow external API call pushes one request over the limit. Here is how to track it down.
Step 1: Read the Log Lines Carefully
Look at the gunicorn output around the failure:
[CRITICAL] WORKER TIMEOUT (pid:12)
[INFO] Worker exiting (pid: 12)
[INFO] Booting worker with pid: 31
Note the time. Then check which request was running. If you only see the worker lines, turn on access logs with request time:
gunicorn app.wsgi:application \
--access-logfile - \
--access-logformat '%(h)s "%(r)s" %(s)s %(D)s'
%(D)s is the request time in microseconds. Sort by it and the slow endpoint stands out.
Step 2: Understand Why Sync Workers Time Out
The default worker class is sync. Each worker handles one request at a time and tells the master process it is alive between requests. If a single request takes longer than --timeout, the master assumes the worker is stuck and kills it. This guide explains the mechanism. The gunicorn settings reference lists the default and options.
So the timeout is a symptom of one slow request, not a gunicorn bug.
Step 3: Find the Slow Code
Common causes in Django apps:
- A queryset without an index on a growing table, or an N+1 loop in a template.
- A call to an external API (payments, email, an LLM) with no timeout set.
- Work at import time, such as loading a large file or ML model when the module loads. One gunicorn issue describes 502s on a Django app loading a large pickle file.
Use Django Debug Toolbar locally, or log slow queries in production. For N+1 queries:
# slow: one query per order
for order in Order.objects.all():
print(order.customer.name)
# fast: one join
for order in Order.objects.select_related("customer"):
print(order.customer.name)
Always set a timeout on outbound calls:
import requests
r = requests.get(url, timeout=(3, 10)) # connect, read
Without it, one slow vendor can block every worker. Our guide on API timeout errors and retries explains the pattern for Node.js, and the same logic applies in Python. If the slowness is the database, see why a Postgres query is slow in production.
Step 4: Move Long Work Out of the Request
Anything that takes more than a few seconds belongs in a background job: report generation, bulk emails, file processing, calls to slow APIs. Return a response fast and let Celery, RQ or Django-Q do the work.
@app.task
def build_report(report_id):
...
def request_report(request):
report = Report.objects.create(user=request.user)
build_report.delay(report.id)
return JsonResponse({"id": report.id, "status": "queued"}, status=202)
Step 5: Match Proxy and Gunicorn Timeouts
If nginx or a load balancer sits in front, its timeout must be longer than gunicorn's. Otherwise the proxy gives up first and you see a 502 or 504 while gunicorn is still working.
location / {
proxy_pass http://127.0.0.1:8000;
proxy_read_timeout 60s;
}
gunicorn app.wsgi:application --timeout 45
Here gunicorn gives up at 45 seconds and nginx waits 60, so gunicorn answers first. Many platforms also have their own idle timeout, so check yours. For the Nginx side, see Nginx 502 Bad Gateway fixes, which applies to any upstream.
Step 6: Fix Docker and Resource Settings
In Docker, gunicorn's heartbeat file lives in /tmp, which can be slow on some container filesystems. The recommended fix is to use memory:
gunicorn app.wsgi:application --worker-tmp-dir /dev/shm
Also check memory limits. If the container is too small, workers get killed or crawl. A common starting point from the gunicorn docs is (2 x CPU cores) + 1 workers. Add --max-requests 1000 --max-requests-jitter 100 to recycle workers and limit leaks.
Step 7: Check Startup After a Deploy
If the 502 appears only for the first minutes after a deploy, workers may be slow to boot. Look for heavy code in settings.py, app ready() methods or module level code. Use --preload carefully and keep startup work small. Add a health check endpoint so your platform only sends traffic once the app is ready.
Step 8: Pick the Right Worker Type
If your app spends most of its time waiting on other services, sync workers are a poor fit, because each waiting request blocks a whole worker. Options:
--threads 4with thegthreadworker class lets one worker handle several waiting requests. This is a simple first change for Django apps that call external APIs.- Async workers such as
uvicorn.workers.UvicornWorkersuit ASGI Django or FastAPI apps.
gunicorn app.wsgi:application --worker-class gthread --workers 3 --threads 4 --timeout 45
Threads share memory and the Django ORM opens one connection per thread, so check your database connection limit before raising the count. This is the same limit that causes trouble in connection pool exhaustion on other stacks.
How Relia Helps
A timeout is easier to fix when you can see the slow request, the deploy that came before it and the users who got the 502. Relia watches the APIs, database and servers of a live product, joins related signals into one incident explained in plain words, and shows who was affected. After a deploy it checks whether the fix worked. Relia has a Python package, and you can add uptime checks on key pages. There is a Free plan, and a live demo at app.tryrelia.com/demo.
FAQ
What does [CRITICAL] WORKER TIMEOUT mean in gunicorn?
A worker did not respond within --timeout seconds, so the master process killed and replaced it. The cause is usually one slow request.
Should I just increase the gunicorn timeout?
Only as a short-term step. A higher value hides slow code and lets slow requests hold workers longer. Fix the request or move it to a background job.
Why do I get 502 only after a deploy?
New workers may be booting slowly, or the deploy added a slow query or heavy import. Check startup time and the first failing request.
Is gunicorn the same as the 502 from nginx?
No. Nginx returns the 502 because its upstream (gunicorn) closed the connection or did not answer. The gunicorn log tells you why.