시스템 관리_24 Nginx FastCGI 및 Reverse Proxy 환경 504 Gateway Time-out 오류 원인 분석과 완벽 해결 가이드

 
  • 출처 / 참고: Nginx Official Documentation (Module ngx_http_proxy_module, Module ngx_http_fastcgi_module), PHP-FPM Documentation (php-fpm.conf)

명령어: nginx, curl, tail, ss, top, php-fpm
키워드: 504 Gateway Time-out, upstream timed out, proxy_read_timeout, fastcgi_read_timeout, max_execution_time, request_terminate_timeout
사용처: 웹 서비스 대용량 데이터 처리 및 파일 업로드 시 504 응답 해결, 백엔드 애플리케이션(WAS, PHP-FPM) 병목 진단 및 타임아웃 튜닝


실행예제

클라이언트 요청 시 Nginx가 백엔드(업스트림) 서버로부터 응답을 제시간에 받지 못해 발생하는 504 Gateway Time-out 상태를 확인하고, Nginx 에러 로그와 백엔드 소켓/프로세스 상태를 점검합니다.

# 1. 클라이언트 관점: curl 명령어로 504 응답 코드 및 소요 시간 확인
$ curl -Iv -w "\nHTTP Code: %{http_code}\nTime Total: %{time_total}s\n" https://example.com/api/heavy-task
* Connected to example.com (192.168.1.100) port 443
< HTTP/1.1 504 Gateway Time-out
< Server: nginx
< Content-Type: text/html
HTTP Code: 504
Time Total: 60.005s
# -> 약 60초(기본 타임아웃 값) 직후 정확히 504가 반환되는지 확인

# 2. Nginx 에러 로그에서 upstream timed out 세부 원인 확인
$ sudo tail -n 50 /var/log/nginx/error.log
# [출력 예시 - Reverse Proxy (Node.js/Tomcat/Gunicorn 등)]
# [error] 1420#1420: *120 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.10, server: example.com, request: "POST /api/export HTTP/1.1", upstream: "http://127.0.0.1:8080/api/export", host: "example.com"
# [출력 예시 - FastCGI (PHP-FPM)]
# [error] 1420#1420: *121 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.10, server: example.com, request: "GET /report.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"

# 3. 백엔드 업스트림 포트 또는 Unix Domain Socket 수신 상태 및 큐(Send-Q/Recv-Q) 적체 점검
$ sudo ss -lntp '( sport = :8080 or sport = :9000 )'
# 또는 PHP-FPM 유닉스 소켓 버퍼 확인
$ sudo ss -lxp | grep -E 'php|fpm'

# 4. 백엔드 워커 프로세스가 가용한 상태인지 점검 (PHP-FPM 프로세스 고갈 여부 등)
$ ps aux | grep -E 'php-fpm|java|gunicorn' | grep -v grep | wc -l

   

스크립트

Nginx 설정 파일에서 지정된 프록시/FastCGI 타임아웃 값과 실제 백엔드 애플리케이션(PHP-FPM 등)의 실행 제한 시간을 추출 및 비교하고, 현재 백엔드 프로세스의 CPU 점유율 및 풀(Pool) 고갈 상태를 진단하는 쉘 스크립트입니다.

#!/bin/bash
# diagnose_504_timeout.sh - Diagnose Nginx proxy/FastCGI timeouts and backend status

echo "=========================================="
echo " [Nginx 504 Gateway Time-out 원인 진단]"
echo "=========================================="

# 1. Nginx 에러 로그 내 최신 504 upstream timeout 발생 여부 점검
NGINX_LOG="/var/log/nginx/error.log"
echo -e "\n[1] Nginx 업스트림 타임아웃 로그 점검:"
if [ -f "$NGINX_LOG" ]; then
    TIMEOUT_ERRORS=$(grep -E "upstream timed out.*while reading response" "$NGINX_LOG" | tail -n 3)
    if [ -n "$TIMEOUT_ERRORS" ]; then
        echo "  [경고] 최근 타임아웃 로그가 감지되었습니다:"
        echo "$TIMEOUT_ERRORS" | sed 's/^/    /'
    else
        echo "  [정상] 최근 upstream timed out 에러 로그가 없습니다."
    fi
else
    echo "  [안내] 로그 파일을 찾을 수 없습니다: $NGINX_LOG"
fi

# 2. Nginx 타임아웃 관련 설정값 파싱
echo -e "\n[2] Nginx 설정 파일 내 타임아웃 지시어 점검:"
grep -rnE "(proxy_read_timeout|fastcgi_read_timeout|send_timeout)" /etc/nginx/ 2>/dev/null | grep -v "#" | head -n 10 | sed 's/^/  /'
if [ $? -ne 0 ]; then
    echo "  [기본값 사용 중] 별도 지시어가 없어 기본값(60s)으로 동작 중일 가능성이 높습니다."
fi

# 3. 백엔드 애플리케이션 데몬 상태 확인 (PHP-FPM / WAS)
echo -e "\n[3] 백엔드 데몬 구동 및 워커 부하 상태:"
FPM_PROCS=$(pgrep -fc "php-fpm: pool" 2>/dev/null)
JAVA_PROCS=$(pgrep -fc "java" 2>/dev/null)

echo "  - 감지된 PHP-FPM 워커 프로세스 수 : ${FPM_PROCS:-0}개"
echo "  - 감지된 Java(Tomcat 등) 프로세스 수: ${JAVA_PROCS:-0}개"

# PHP-FPM 설정 점검 (설치되어 있을 경우)
PHP_INI=$(php -i 2>/dev/null | grep "Loaded Configuration File" | awk '{print $NF}')
if [ -n "$PHP_INI" ] && [ -f "$PHP_INI" ]; then
    echo -e "\n[4] PHP-FPM 실행 제한 설정 점검 (${PHP_INI}):"
    MAX_EXEC=$(grep -E "^max_execution_time" "$PHP_INI" | head -n 1)
    echo "  - $MAX_EXEC"
fi

# 4. 상위 CPU 점유 백엔드 프로세스 Top 3 추출
echo -e "\n[5] 리소스 과점 백엔드 프로세스 확인:"
ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%cpu | grep -E "php-fpm|java|node|gunicorn" | grep -v grep | head -n 3 | sed 's/^/  /'

echo -e "\n=========================================="
echo " [진단 완료]"
echo "=========================================="

   

해설

504 Gateway Time-out은 게이트웨이나 프록시 역할을 하는 Nginx가 클라이언트 요청을 백엔드 서버(FastCGI/PHP-FPM, Tomcat, Node.js, Python Gunicorn 등)로 전달했으나, Nginx에 지정된 대기 제한 시간 동안 백엔드로부터 아무런 응답을 수신하지 못했을 때 클라이언트에게 전달하는 HTTP 표준 상태 코드입니다.

1. 502 Bad Gateway와의 결정적 차이점

  • 502 Bad Gateway: 백엔드 프로세스가 완전히 꺼져 있거나 포트가 닫혀 있어 Nginx가 즉시 Connection refused 응답을 받거나 비정상적인 헤더를 전달받은 상태.
  • 504 Gateway Time-out: 백엔드와 연결(Connect)은 성공했으나, 백엔드가 무거운 작업 처리, DB 락(Lock), 외부 API 지연, 워커 풀 고갈 등으로 인해 정해진 시간 동안 응답을 끝내지 못하고 있는 상태.

2. 주요 원인 및 계층별 해결 방법

1) Nginx의 읽기 타임아웃 기본값(60초) 초과

배치 작업, 대용량 엑셀 다운로드, 파일 업로드/변환 요청 등은 60초 이상 소요될 수 있습니다.

  • Reverse Proxy 환경 설정 (nginx.conf 또는 conf.d/*.conf):
    location /api/heavy/ {
        proxy_pass http://127.0.0.1:8080;
          
        # 타임아웃을 기본 60초에서 300초(5분)로 증설
        proxy_connect_timeout 300s;
        proxy_send_timeout    300s;
        proxy_read_timeout    300s;
    }
    
  • FastCGI (PHP-FPM) 환경 설정:
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
          
        # FastCGI 읽기 타임아웃 증설
        fastcgi_read_timeout 300s;
        fastcgi_send_timeout 300s;
    }
    

2) 백엔드 런타임 제한 시간과의 불일치

Nginx 타임아웃만 늘린다고 해결되지 않습니다. 백엔드 자체의 타임아웃 제한도 함께 늘려주어야 합니다.

  • PHP 및 PHP-FPM 환경:
    1. /etc/php/8.2/fpm/php.ini:
      max_execution_time = 300
      
    2. /etc/php/8.2/fpm/pool.d/www.conf:
      request_terminate_timeout = 300
      
  • 주의: Nginx fastcgi_read_timeout보다 request_terminate_timeout이 짧으면 FastCGI 프로세스가 먼저 종료되어 504 대신 502 에러가 발생할 수 있습니다.

3) 백엔드 동시성 한계 및 워커 풀(Worker Pool) 고갈

트래픽이 증가했을 때 백엔드 워커(PHP-FPM의 pm.max_children, 톰캣의 maxThreads)가 가득 차면, 뒤이어 들어오는 요청은 백엔드 대기열 큐(Queue)에서 머무르다가 Nginx 타임아웃에 걸려 504를 뱉게 됩니다. 백엔드 서버의 스펙에 맞추어 워커 수를 적정량 증설해야 합니다.

   

주의사항

  1. 무분별한 타임아웃 연장 금지:
    • 타임아웃(proxy_read_timeout)을 무작정 600s, 1200s 등으로 늘려놓으면, 슬로우 쿼리나 데드락이 발생했을 때 Nginx와 백엔드 워커가 장시간 요청을 물고 늘어져 워커 커넥션 풀이 빠르게 고갈됩니다. 시간이 오래 걸리는 작업은 동기 HTTP 요청으로 처리하지 말고 메시지 큐(RabbitMQ, Kafka, Redis Celery)를 통한 비동기 백그라운드 작업으로 아키텍처를 개선해야 합니다.
  2. 클라우드 로드밸런서(AWS ALB 등)의 타임아웃 체인 고려:
    • Nginx 앞단에 AWS ALB나 Cloudflare 같은 L7 프록시가 위치해 있다면, 이들의 기본 유휴 제한 시간(Idle Timeout, 기본 60초)도 함께 증설해야 합니다. ALB(60s) < Nginx(300s)인 구조에서는 Nginx가 300초를 기다려도 앞단 ALB가 먼저 60초 만에 504를 반환합니다.
  3. 설정 수정 후 데몬 리로드 필수:
    • 설정 파일을 수정한 후에는 반드시 문법 검증 후 설정을 반영해야 합니다:
      sudo nginx -t && sudo systemctl reload nginx
      sudo systemctl reload php8.2-fpm  # PHP 사용 시