Skip to content
Elephant House Logo

February 3, 2026

The Gunicorn Timeout Trinity

When running Gunicorn in production, specifically behind a Load Balancer like AWS ELB, you are juggling three distinct timeout settings. Getting the relationship between them wrong leads to 502s, dropped connections, or ugly tracebacks.

Here is how they interact and how to configure them correctly.

1. timeout

Default: 30s

The “Heartbeat” check. If a worker is silent (processing a heavy request or frozen) for this long, the Master process kills it.

Rule: Must be longer than graceful_timeout.

Why: If timeout is shorter than graceful_timeout, a worker trying to gracefully finish a long request during a shutdown will be killed by the standard timeout check before it has a chance to exit cleanly.

2. graceful_timeout

Default: 30s

The “Shutdown Timer”. When you redeploy or reload Gunicorn, workers receive a signal to stop. They stop accepting new requests but continue processing the current one. This setting dictates how long they have to finish that work.

Rule: Should be longer than your slowest expected request.

3. keepalive

Default: 2s

The “Connection Hold”. How long a TCP connection waits for a subsequent request.

Rule: Must be longer than your Load Balancer’s idle timeout (AWS ELB defaults to 60s).

Why: As I wrote previously, if Gunicorn closes the connection while the ELB thinks it’s still open, the ELB will try to send a user request down a dead tube, resulting in a 502 Bad Gateway.

Summary Configuration

Your gunicorn.conf.py should look something like this:

# 1. Allow workers time to finish during restarts
graceful_timeout = 30

# 2. Ensure standard timeout doesn't kill them while they are finishing
#    (Set this comfortably higher than graceful_timeout)
timeout = 60

# 3. Keep connections open longer than the ELB (60s) to avoid 502s
keepalive = 75

Related Posts

See all thoughts