nginx 504 Gateway Timeout Fix Guide: From proxy_read_timeout to Upstream Timeouts
The deploy went fine, but as soon as you hit a heavy report API or an LLM response call, a 504 Gateway Timeout appears at exactly 30 or 60 seconds. Sound familiar? A 504 means nginx forwarded the request to the upstream (app server) just fine, then cut the connection because no response came back in time. The key point: this is a timeout problem, not a connection failure.
This post walks through diagnosing where the 504 is happening, fixing it by pasting timeout directives in the right place, and aligning timeouts in multi-hop setups such as ALB, nginx, and gunicorn.
First, Tell 504 and 502 Apart Correctly
504 and 502 look similar on the surface but have completely different causes. One-line summary: 504 = the upstream is alive but did not respond in time; 502 = the upstream is dead or the response is broken.
| Aspect | 504 Gateway Timeout | 502 Bad Gateway |
|---|---|---|
| Meaning | Timeout (slow response) | Connection failure / broken response |
| When it happens | After a fixed delay (30s / 60s) | Immediately or mid-processing |
| Typical log | upstream timed out (110: Connection timed out) | connect() failed (111: Connection refused) |
| First suspects | Slow queries / external APIs, proxy_read_timeout | App server down, worker crash |
If you suspect a 502, the cause is different—see the [nginx 502 Bad Gateway troubleshooting guide]. This post focuses on timeouts.
Diagnose by Location: Client → nginx → Upstream
Do not blindly raise proxy_read_timeout. First figure out where the connection was cut.
Step 1 — Measure wait time from the client
curl -w "@-" -o /dev/null -s https://example.com/slow-api <<'EOF'
time_connect: %{time_connect}s
time_starttransfer: %{time_starttransfer}s
time_total: %{time_total}s
EOFIf time_total cuts off at exactly 60 seconds (or 30 seconds), a timeout is almost certain.
Step 2 — Check nginx error.log
tail -f /var/log/nginx/error.logIf you see upstream timed out ... while reading response header from upstream, you hit nginx's read timeout.
Step 3 — Call the upstream directly
curl -w "%{time_total}\n" -o /dev/null -s http://localhost:8000/slow-apiIf it is still slow or gets cut off here, the culprit is the upstream (app server), not nginx. Raising nginx timeouts alone will not help.
Copy-Paste nginx Timeout Directives
The usual 504 culprit is proxy_read_timeout (default 60s). Put it in a server or location block and reload.
location /api/ {
proxy_pass http://backend;
proxy_connect_timeout 5s; # 업스트림 TCP 연결 대기 (보통 짧게)
proxy_send_timeout 60s; # nginx → 업스트림 요청 전송 대기
proxy_read_timeout 300s; # 업스트림 응답 대기 (504의 주범!)
}For FastCGI setups such as PHP-FPM, the directives are different.
location ~ \.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_connect_timeout 5s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 300s; # FastCGI의 504 주범
}After changing the config, always syntax-check and reload.
nginx -t && nginx -s reloadPractical tip: Do not set 300s for the entire site. Split slow endpoints into their own
locationand give those a long timeout; leave the rest at the default. If large uploads are getting cut off, it may be a request-size limit rather than a timeout—see the [nginx 413 troubleshooting guide] as well.
When the Upstream Is the Real Culprit
The most common mistake is raising only the nginx timeout and leaving the upstream as-is, so you still get 504s. You have to raise the app server timeout too.
| App server | Directive | Default | Notes |
|---|---|---|---|
| gunicorn | --timeout | 30s | Classic 504 cause; worker is killed if it does not respond |
| php-fpm | request_terminate_timeout | 0 (unlimited) | Too few pm.max_children → workers queue up → 504 |
| uWSGI | harakiri | none | Worker is force-killed on exceed |
For gunicorn, raise it like this:
gunicorn app:app --workers 4 --timeout 300If php-fpm does not have enough workers, requests pile up in the queue and you get 504s. Increase pm.max_children to match your traffic.
Aligning Timeouts Across Multi-Hop Proxies
In microservice and container setups, multi-hop chains such as ALB → nginx-ingress → nginx → gunicorn are common, and timeout-alignment issues have become really frequent. The rule is simple: the outer hop must be longer.
ALB idle timeout (60s+) ≥ nginx proxy_read_timeout ≥ gunicorn --timeoutIf this order is inverted—for example, if the ALB idle timeout (60s) is shorter than nginx (300s)—the ALB will drop the connection while nginx is still waiting, and you get a 504. This has been showing up a lot recently with streaming backends that proxy LLM APIs. The longer the response, the more you should start by checking the outermost LB's idle timeout.
Mapping Log Messages to Causes
| Log message | Cause | What to fix |
|---|---|---|
upstream timed out ... while reading response header | Response is too slow | proxy_read_timeout + upstream processing speed |
upstream timed out ... while connecting to upstream | Connection delay / upstream overload | proxy_connect_timeout, worker count |
| Nothing in the nginx logs | Cut off in front of nginx (LB) | ALB/ELB idle timeout |
30-Second Checklist
- Use
curl -wto see at how many seconds it cuts off (30/60/300?) - In error.log, check where the
timed outmessage appears (readingvsconnecting) - Call the upstream directly with
curl localhost:8000to identify the real culprit - Split slow endpoints into their own
locationand adjustproxy_read_timeout - Also raise the upstream timeout (e.g. gunicorn
--timeout) - Confirm the order ALB ≥ nginx ≥ app server
nginx -t && nginx -s reload
References: Official Docs
The primary source for the behavior, settings, and errors covered here is the official documentation below. Check it for version-specific options and exact behavior.
FAQ
Q. Can I just raise the timeout indefinitely? A. No. Raising timeouts is first aid only. The root cause is usually a slow DB query or a delayed external API. A longer timeout means nginx connections stay occupied longer, so throughput drops. Fix the actual response time with query indexes, caching, and async processing.
Q. I get a 504 but nothing shows up in the nginx logs. A. It was probably cut off in front of nginx (ALB/ELB, CDN). First check whether the LB idle timeout is shorter than nginx.
Q. I get 504s on WebSocket/SSE.
A. Streaming and long-polling connections keep sending data after the response headers, so set proxy_read_timeout long enough (very long for SSE). For WebSocket, also confirm proxy_http_version 1.1 and that the Upgrade/Connection headers are forwarded.
Q. How is this different from the 502 post? A. 504 is a timeout problem: the upstream is alive but slow. 502 is a connection problem: the upstream is dead or the response is broken. For 502, see the [nginx 502 Bad Gateway troubleshooting guide].
Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
Comments
Be the first to comment.