Nginx 502·504 오류 해결: upstream 로그로 원인 찾는 순서
AI Editter
Nginx 502와 504 오류는 둘 다 뒷단 서버와 통신하는 과정에서 나타날 수 있지만, 고치는 위치는 같지 않습니다. 502는 연결 거절·연결 종료·잘못된 응답을, 504는 연결이나 응답을 기다리다 제한 시간을 넘긴 상황을 먼저 조사합니다. 상태 코드만 보고 timeout을 늘리면 연결 포트 오류는 그대로 남고 장애를 발견하는 시간만 길어질 수 있습니다.
여기서는 Nginx가 HTTP 애플리케이션으로 요청을 전달하는 구성을 다룹니다. PHP-FPM의 FastCGI 연결은 로그를 읽는 순서는 비슷하지만 설정이 fastcgi_*이므로 proxy_* 예시를 그대로 적용하면 안 됩니다.
브라우저 화면보다 먼저 볼 것은 error.log의 마지막 구절
재현 시각, 요청 URL, 상태 코드, 요청 ID를 기록한 뒤 같은 시간의 access log와 error log를 찾습니다. 배포 환경마다 경로가 다르며 컨테이너는 표준 출력으로 보낼 수도 있습니다. 아래 표의 오류 문자열은 진단용 예시입니다.
| 로그에서 보이는 내용 | 먼저 의심할 지점 | 다음 확인 |
|---|---|---|
| connect() failed … Connection refused | 리스너가 없거나 명시적으로 거절 | 서비스 상태, 포트, 실제 바인딩 주소 |
| upstream timed out … while connecting to upstream | 연결 수립 지연 | 경로, 방화벽, 연결 대기열 |
| upstream timed out … while reading response header | 요청 처리 후 헤더가 늦음 | 앱 스레드·DB 대기·외부 API |
| upstream prematurely closed connection | 뒷단이 응답 완료 전에 종료 | 앱 오류, 재시작, OOM, 앱 자체 timeout |
예를 들어 while connecting과 while reading response header는 조사할 단계가 다릅니다. 따라서 “504면 연결은 성공했다”는 식으로 판단하지 않습니다. 재시도하는 upstream이 여러 개라면 한 요청에 여러 실패가 남을 수도 있습니다.
1. Nginx가 있는 위치에서 upstream을 직접 호출한다
sudo nginx -t
sudo ss -lntp
curl --connect-timeout 3 --max-time 10 \
-H 'Host: app.example.com' \
-o /dev/null -sS \
-w 'status=%{http_code} connect=%{time_connect} first_byte=%{time_starttransfer} total=%{time_total}\n' \
http://127.0.0.1:8080/health
주소·포트·Host·경로는 실제 upstream에 맞춥니다. /health가 없는 서비스라면 가벼운 읽기 전용 경로로 바꾸세요. HTTPS upstream은 올바른 인증서 이름과 SNI도 맞춰야 하므로 HTTP 예시로 바꿔 시험하면 같은 조건의 검사가 아닙니다.
Nginx가 컨테이너 안에 있으면 그 안의 127.0.0.1은 호스트나 다른 앱 컨테이너가 아닙니다. 같은 네트워크 네임스페이스에서 테스트하거나 승인된 진단 컨테이너를 사용하세요. 호스트에서는 200인데 Nginx 컨테이너에서는 연결되지 않는다면 앱 코드보다 네트워크·서비스 이름·포트 공개 범위를 먼저 봅니다.
2. 어디서 시간이 쓰이는지 access log에 남긴다
다음은 진단용 로그 형식 예시입니다. log_format은 http 블록에, access_log는 조사할 server 블록에 합칩니다. 요청 본문이나 인증 헤더를 기록할 필요는 없습니다.
log_format upstream_timing '$time_iso8601 status=$status '
'method=$request_method uri=$uri upstream=$upstream_addr '
'ustatus=$upstream_status connect=$upstream_connect_time '
'header=$upstream_header_time upstream_time=$upstream_response_time '
'request_time=$request_time';
access_log /var/log/nginx/upstream-timing.log upstream_timing;
검사 후 설정을 reload하고 동일 요청을 소량 재현합니다. connect가 짧고 header가 길다면 애플리케이션이 헤더를 만들기 전의 대기를 좁힐 수 있습니다. 전체 request_time만 길다면 클라이언트 전송·응답 크기 등 다른 구간도 봐야 합니다. 값이 여러 개이거나 -로 남는 경우에는 단일 숫자처럼 더하지 않습니다. 변수의 단위와 의미는 Nginx upstream 변수 문서에서 확인할 수 있습니다.
3. timeout은 작업 성격을 확인한 뒤 조정한다
proxy_connect_timeout은 연결 수립, proxy_read_timeout은 upstream의 연속된 읽기 사이 대기 시간에 관련됩니다. 후자는 요청 전체의 총 실행 시간을 뜻하지 않습니다. 공식 proxy 모듈 설명 기준으로 구분해야 합니다.
30초 안에 끝나던 조회가 갑자기 60초를 넘겼다면 DB 잠금, 실행 계획 변화, 외부 API 실패를 조사합니다. 원래 몇 분 걸리는 내보내기는 작업 ID를 먼저 반환하고 완료 상태를 조회하는 방식이 더 적합할 수 있습니다. timeout을 늘리는 동안 동시 요청이 쌓이면 워커와 연결 풀이 더 빨리 고갈될 수 있습니다.
다시 배포하기 전에 남길 장애 기록
“Nginx 재시작 후 정상”으로 끝내지 말고 실패 요청 1건의 경로, upstream 주소, 오류 구절, 소요 시간, 앱 로그를 함께 남기세요. 수정 후에는 정상 요청과 실패하던 요청을 모두 재검사하고, 실패율과 지연 시간이 기존 수준으로 돌아왔는지 확인합니다. 여러 upstream 중 한 대만 실패하는 경우에는 각 서버를 따로 시험해야 합니다.
사이트 앞에 Cloudflare가 있다면 브라우저에 표시된 코드가 원본 Nginx의 코드인지도 구분해야 합니다. CDN을 통과한 응답과 원본 서버의 같은 시각 로그를 맞추면 장애 구간이 줄어듭니다. 로그 수집이 끝나면 임시 진단 로그의 보존 기간과 디스크 사용량도 정리하세요.