nginx 504 Gateway Timeout 해결 가이드: proxy_read_timeout부터 업스트림 타임아웃까지
배포는 잘 됐는데 무거운 리포트 API나 LLM 응답 호출만 들어가면 정확히 30초, 60초쯤에 504 Gateway Timeout이 뜬다. 익숙하시죠? 504는 "nginx가 업스트림(WAS)에게 요청은 잘 넘겼는데, 정해진 시간 안에 응답이 안 와서 끊었다"는 신호입니다. 즉 연결 실패가 아니라 타임아웃 문제라는 게 핵심입니다.
이 글은 504가 "어디서" 발생했는지 진단 순서로 좁히고, 타임아웃 지시어를 정확한 위치에 복붙해 해결하며, ALB·nginx·gunicorn 같은 다단 구성에서 타임아웃을 정렬하는 실무 노하우까지 한 번에 정리합니다.
504 vs 502부터 정확히 구분하자
504와 502는 증상이 비슷해 보이지만 원인이 완전히 다릅니다. 한 줄 요약: 504 = 업스트림이 살아있지만 제때 응답을 못 함, 502 = 업스트림이 죽었거나 응답이 깨짐.
| 구분 | 504 Gateway Timeout | 502 Bad Gateway |
|---|---|---|
| 의미 | 타임아웃 (응답 지연) | 연결 실패 / 응답 깨짐 |
| 발생 시점 | 요청 후 일정 시간 경과(30s·60s) | 즉시 또는 처리 중 |
| 대표 로그 | upstream timed out (110: Connection timed out) | connect() failed (111: Connection refused) |
| 첫 의심 대상 | 느린 쿼리·외부 API, proxy_read_timeout | WAS 다운, 워커 크래시 |
502가 의심된다면 원인이 다르니 [nginx 502 Bad Gateway 해결 가이드]를 참고하세요. 이 글은 타임아웃에 집중합니다.
발생 위치별 진단 순서: 클라이언트 → nginx → 업스트림
무작정 proxy_read_timeout부터 늘리지 마세요. 어디서 끊겼는지부터 확인하는 게 순서입니다.
1단계 — 클라이언트에서 대기 시간 측정
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
EOFtime_total이 정확히 60초(또는 30초)에서 끊긴다면 타임아웃이 거의 확실합니다.
2단계 — nginx error.log 확인
tail -f /var/log/nginx/error.logupstream timed out ... while reading response header from upstream이 보이면 nginx의 읽기 타임아웃에 걸린 겁니다.
3단계 — 업스트림을 직접 호출
curl -w "%{time_total}\n" -o /dev/null -s http://localhost:8000/slow-api여기서도 느리거나 끊긴다면 범인은 nginx가 아니라 **업스트림(WAS)**입니다. 이 경우 nginx 타임아웃만 늘려도 소용없습니다.
nginx 타임아웃 지시어 복붙 예제
504의 주범은 대부분 proxy_read_timeout입니다(기본 60s). server 또는 location 블록에 넣고 리로드하세요.
location /api/ {
proxy_pass http://backend;
proxy_connect_timeout 5s; # 업스트림 TCP 연결 대기 (보통 짧게)
proxy_send_timeout 60s; # nginx → 업스트림 요청 전송 대기
proxy_read_timeout 300s; # 업스트림 응답 대기 (504의 주범!)
}PHP-FPM 같은 FastCGI 환경이라면 지시어가 다릅니다.
location ~ \.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_connect_timeout 5s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 300s; # FastCGI의 504 주범
}설정 후 반드시 문법 검사와 리로드를 합니다.
nginx -t && nginx -s reload실무 팁: 전체 사이트에 300초를 거는 건 위험합니다. 느린 엔드포인트만 별도
location으로 분리해 길게 주고, 나머지는 기본값을 유지하세요. 대용량 업로드 때문에 끊긴다면 타임아웃이 아니라 요청 크기 제한일 수 있으니 [nginx 413 해결 가이드]도 확인하세요.
업스트림이 진짜 범인일 때
가장 흔한 실수가 nginx 타임아웃만 늘리고 업스트림은 그대로 둬서 여전히 504가 나는 것입니다. WAS 자체 타임아웃도 같이 올려야 합니다.
| WAS | 지시어 | 기본값 | 비고 |
|---|---|---|---|
| gunicorn | --timeout | 30s | 504 단골 원인, 워커가 응답 못 하면 강제 종료 |
| php-fpm | request_terminate_timeout | 0(무제한) | pm.max_children 부족 시 워커 대기 → 504 |
| uWSGI | harakiri | 없음 | 초과 시 워커 강제 kill |
예를 들어 gunicorn은 이렇게 올립니다.
gunicorn app:app --workers 4 --timeout 300php-fpm에서 워커 수가 모자라면 요청이 큐에 쌓이며 504가 납니다. pm.max_children을 트래픽에 맞게 늘리세요.
다단 프록시 타임아웃 정렬 원칙
마이크로서비스·컨테이너 환경에서 ALB → nginx-ingress → nginx → gunicorn 식 다단 구성이 늘면서, 타임아웃 정렬 이슈가 정말 빈번해졌습니다. 원칙은 단순합니다. 바깥쪽이 더 길어야 한다.
ALB idle timeout (60s+) ≥ nginx proxy_read_timeout ≥ gunicorn --timeout이 순서가 어긋나면, 예를 들어 ALB idle timeout(60s)이 nginx(300s)보다 짧으면 nginx가 응답을 기다리는 중에 ALB가 먼저 연결을 끊어 504가 발생합니다. 최근 LLM API를 프록시하는 스트리밍 응답 백엔드에서 이 문제로 504가 급증하는 사례가 많습니다. 응답이 길수록 가장 바깥 LB의 idle timeout부터 점검하세요.
로그 문구별 원인 매핑
| 로그 메시지 | 원인 | 손볼 곳 |
|---|---|---|
upstream timed out ... while reading response header | 응답이 너무 느림 | proxy_read_timeout + 업스트림 처리 속도 |
upstream timed out ... while connecting to upstream | 연결 자체 지연/업스트림 과부하 | proxy_connect_timeout, 워커 수 |
| nginx 로그에 아무것도 없음 | nginx보다 앞단(LB)에서 끊김 | ALB/ELB idle timeout |
30초 체크리스트
curl -w로 몇 초에서 끊기는지 확인 (30/60/300?)- error.log에서
timed out문구 위치 확인 (readingvsconnecting) curl localhost:8000으로 업스트림 직접 호출해 진짜 범인 특정- 느린 엔드포인트만
location분리 후proxy_read_timeout조정 - gunicorn
--timeout등 업스트림 타임아웃도 함께 상향 - ALB ≥ nginx ≥ WAS 순서로 정렬 확인
nginx -t && nginx -s reload
자주 묻는 질문 (FAQ)
Q. 타임아웃을 무한정 늘려도 되나요? A. 안 됩니다. 타임아웃을 늘리는 건 응급처치일 뿐, 근본 원인은 느린 DB 쿼리나 외부 API 지연인 경우가 대부분입니다. 길게 잡으면 그만큼 nginx 커넥션이 점유되어 동시 처리량이 떨어집니다. 쿼리 인덱스, 캐싱, 비동기 처리로 응답 속도 자체를 줄이세요.
Q. 504인데 nginx 로그에 아무것도 안 찍혀요. A. nginx보다 앞단(ALB/ELB, CDN)에서 끊겼을 가능성이 높습니다. LB의 idle timeout이 nginx보다 짧은지 먼저 확인하세요.
Q. WebSocket/SSE에서 504가 나요.
A. 스트리밍·롱폴링 연결은 응답 헤더 이후에도 데이터가 계속 오므로 proxy_read_timeout을 충분히 길게(혹은 SSE는 매우 길게) 설정하고, WebSocket이라면 proxy_http_version 1.1과 Upgrade/Connection 헤더 전달도 함께 확인해야 합니다.
Q. 502 글과 뭐가 다른가요? A. 504는 업스트림이 살아있지만 느린 타임아웃 문제, 502는 업스트림이 죽었거나 응답이 깨진 연결 문제입니다. 502라면 [nginx 502 Bad Gateway 해결 가이드]를 참고하세요.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...