Gunicorn — WORKER TIMEOUT and workers are killed
Diagnose Gunicorn worker timeouts by locating blocking request handlers and tuning timeout and worker class appropriately.
Symptoms
- [CRITICAL] WORKER TIMEOUT appears in the Gunicorn log
- A request is killed and the client sees a 502 or 504
- Workers are restarted repeatedly under load
- The same request works locally but times out in production
Possible causes
- A request handler blocks longer than the configured timeout without yielding
- The worker class is synchronous while the code performs long I/O
- An external dependency is slow or hangs and the handler waits unbounded
- Too few workers cause requests to queue beyond the timeout
Diagnosis steps
Find the slow handler
grep -n "WORKER TIMEOUT" gunicorn.log | tailInterpretation
The surrounding log lines and request path point at the handler or endpoint that exceeded the timeout.
Lower the timeout temporarily to reproduce deterministically
gunicorn app:app --timeout 10 --workers 2 --log-level debugInterpretation
A short timeout makes slow handlers fail fast so you can identify them precisely.
Check worker class and concurrency
gunicorn app:app --worker-class gthread --threads 8Interpretation
Switching to threaded workers shows whether the issue is blocking waits versus genuine long computation.
Root cause
A worker did not report back within the timeout because the handler was blocked, usually on synchronous I/O or on an unresponsive dependency, so Gunicorn killed and replaced the worker.
Fix
Reduce handler time by adding timeouts on outbound calls, offloading slow work to a background job, and choosing a worker class and count that match the workload.
Verify the fix
Re-run the workload and confirm no WORKER TIMEOUT entries, that response times stay under the timeout, and that workers are not restarted repeatedly.
Symptom walkthrough
Gunicorn logs [CRITICAL] WORKER TIMEOUT and kills a worker while a client waits, often returning 502 or 504. Workers may restart in a loop under load, and overall latency climbs because queued requests wait behind a blocked worker. The code may look fine locally where data volumes and network latency are lower.
Fix in practice
Make handlers finish within the timeout budget: set timeouts on database and HTTP calls, move heavy work to a queue, and avoid blocking calls in async handlers. Then choose a worker class and count suited to the workload rather than only raising the timeout, which merely delays the failure.
Verification
Rerun the load and confirm the WORKER TIMEOUT messages stop, response times stay below the limit, and workers are not being recycled repeatedly. Instrumenting handler duration helps confirm the fix rather than hiding it.
Diagnostic commands
Check the behavior with commands like these:
grep -n "WORKER TIMEOUT" gunicorn.log | tail
gunicorn app:app --timeout 10 --workers 2 --log-level debug
gunicorn app:app --worker-class gthread --threads 8