Guides / [CRITICAL] WORKER TIMEOUT in gunicorn (Django)
[CRITICAL] WORKER TIMEOUT in gunicorn (Django)
gunicorn logs [CRITICAL] WORKER TIMEOUT (pid:42) and a request fails with a 500, 502 or 504. A worker didn't report back within 30 seconds (the default), so gunicorn killed it. The timeout is a symptom. Find what made the request slow.
[2026-01-12 10:31:07 +0000] [1] [CRITICAL] WORKER TIMEOUT (pid:42)
[2026-01-12 10:31:08 +0000] [1] [ERROR] Worker (pid:42) was sent SIGKILL! Perhaps out of memory?
What it means
With the default synchronous workers, each worker handles one request at a time and signals to gunicorn that it is alive. If a request keeps the worker busy longer than --timeout, gunicorn assumes it is stuck and kills it. The "Perhaps out of memory?" line is a guess; it's printed for every kill.
Why it happens
- An outgoing call with no timeout.
requests.get(url)waits forever for a slow or dead API, payment provider or email server. - A slow database query, often a missing index, or a lock held by another transaction.
- Too much work in one request: a big export, image processing, a large file upload or an import.
- Too few workers for the traffic, so requests queue behind slow ones.
- A starved server. Not enough CPU or memory, so everything is slow, or the process really did run out of memory.
- Docker's worker heartbeat file is on a slow disk (gunicorn writes to a temp file, which on some container filesystems blocks).
The fix
1. Find the slow request
Turn on logging of access and errors to stdout, with timing:
gunicorn myproject.wsgi:application \
--access-logfile - --error-logfile - --capture-output \
--access-logformat '%(h)s "%(r)s" %(s)s %(L)ss'
%(L)s is the request time in seconds. The path that repeats before each timeout is your culprit.
2. Always set timeouts on outgoing calls
response = requests.get(url, timeout=(3.05, 10)) # connect, read
For the database, add a statement timeout so one bad query can't hold a worker: DATABASES["default"]["OPTIONS"] = {"options": "-c statement_timeout=15000"} (milliseconds, Postgres).
3. Give gunicorn the right shape
gunicorn myproject.wsgi:application \
--workers 3 --worker-class gthread --threads 4 \
--timeout 60 --worker-tmp-dir /dev/shm
--workers: start around two per CPU core, plus one.gthreadwith--threadslets one worker handle several requests that wait on I/O. Watch your database connection count (too many connections).--timeout: raising it only hides a slow request. Do it as a short-term measure.--worker-tmp-dir /dev/shmis the recommended setting for Docker (gunicorn FAQ).
4. Take long work out of the request
If a view legitimately takes a minute, don't make the browser wait for it. Break it up, run it as a separate process, or stream the response. Timeouts of a few seconds on a web request are a feature.
Making it stick in production
- Track the slowest endpoints, and set an alert when a request exceeds a few seconds.
- Use
--max-requests 1000 --max-requests-jitter 100so leaky workers restart themselves. - Match the proxy's timeout to gunicorn's (nginx
proxy_read_timeout, load balancer idle timeout). Mismatches produce 502s and 504s; see 502 Bad Gateway. - Watch CPU and memory, and scale up when they are consistently high.
Or skip it
Django Cloud lets you pick a server size from Nano to XLarge, run 1 to 20 instances and scale from the dashboard or with djangocloud scale. CPU and memory charts and alerts show when you need more.
pip install djangocloud-cli
djangocloud deploy
Free plans are available for students, AWS Activate startups, open-source, non-profit and research projects. See how Django Cloud compares with other ways to host Django.
Django is a registered trademark of the Django Software Foundation. Django Cloud is not affiliated with or endorsed by it. Found a mistake in this guide? Tell us and we will fix it.