/인프라/Fixing nginx 504 Gateway Timeout: proxy_read_timeout and Upstream Timeouts
Infrastructurenginx504 gateway timeout

Fixing nginx 504 Gateway Timeout: proxy_read_timeout and Upstream Timeouts

A practical guide to pinpointing nginx 504 Gateway Timeouts by where they occur, then fixing them with copy-paste examples for proxy_read_timeout, gunicorn and php-fpm upstream timeouts, and aligning timeouts across multi-hop proxies like A

Fixing nginx 504 Gateway Timeout: proxy_read_timeout and Upstream Timeouts

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.

Aspect504 Gateway Timeout502 Bad Gateway
MeaningTimeout (slow response)Connection failure / broken response
When it happensAfter a fixed delay (30s / 60s)Immediately or mid-processing
Typical logupstream timed out (110: Connection timed out)connect() failed (111: Connection refused)
First suspectsSlow queries / external APIs, proxy_read_timeoutApp 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

Bash
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
EOF

If time_total cuts off at exactly 60 seconds (or 30 seconds), a timeout is almost certain.

Step 2 — Check nginx error.log

Bash
tail -f /var/log/nginx/error.log

If you see upstream timed out ... while reading response header from upstream, you hit nginx's read timeout.

Step 3 — Call the upstream directly

Bash
curl -w "%{time_total}\n" -o /dev/null -s http://localhost:8000/slow-api

If 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.

Nginx
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.

Nginx
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.

Bash
nginx -t && nginx -s reload

Practical tip: Do not set 300s for the entire site. Split slow endpoints into their own location and 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 serverDirectiveDefaultNotes
gunicorn--timeout30sClassic 504 cause; worker is killed if it does not respond
php-fpmrequest_terminate_timeout0 (unlimited)Too few pm.max_children → workers queue up → 504
uWSGIharakirinoneWorker is force-killed on exceed

For gunicorn, raise it like this:

Bash
gunicorn app:app --workers 4 --timeout 300

If 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.

CODE
ALB idle timeout (60s+) ≥ nginx proxy_read_timeout ≥ gunicorn --timeout

If 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 messageCauseWhat to fix
upstream timed out ... while reading response headerResponse is too slowproxy_read_timeout + upstream processing speed
upstream timed out ... while connecting to upstreamConnection delay / upstream overloadproxy_connect_timeout, worker count
Nothing in the nginx logsCut off in front of nginx (LB)ALB/ELB idle timeout

30-Second Checklist

  1. Use curl -w to see at how many seconds it cuts off (30/60/300?)
  2. In error.log, check where the timed out message appears (reading vs connecting)
  3. Call the upstream directly with curl localhost:8000 to identify the real culprit
  4. Split slow endpoints into their own location and adjust proxy_read_timeout
  5. Also raise the upstream timeout (e.g. gunicorn --timeout)
  6. Confirm the order ALB ≥ nginx ≥ app server
  7. 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].

확인 정보
✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 · ·업데이트 ·
관련 공식 문서NGINX 공식 문서

Comments

Be the first to comment.