/인프라/nginx 502 Bad Gateway 원인 진단표·복붙 명령어로 5분 해결
인프라nginx502 bad gateway

nginx 502 Bad Gateway 원인 진단표·복붙 명령어로 5분 해결

nginx 502 Bad Gateway, 무작정 재시작 마세요. 원인별 진단표와 error.log 키워드 매칭, php-fpm·gunicorn·uvicorn 업스트림별 복붙 명령어로 원인을 특정하고 SELinux·소켓 권한 함정까지 빠르게 해결합니다.

nginx 502 Bad Gateway 원인 진단표·복붙 명령어로 5분 해결

nginx 502 Bad Gateway 원인 진단표와 복붙 명령어로 5분 해결

프론트엔드는 멀쩡한데 왜 502인가

장애 알림이 울리고 브라우저에 502 Bad Gateway가 떴습니다. 가장 먼저 손이 가는 건 systemctl restart nginx겠지만, 잠깐 멈추세요. 502는 nginx의 잘못이 아닐 가능성이 훨씬 큽니다.

502가 4xx와 결정적으로 다른 점은 요청 자체는 정상이라는 것입니다. 4xx는 클라이언트(요청)의 문제지만, 502는 nginx가 클라이언트의 요청을 받아 뒤쪽 업스트림(php-fpm, gunicorn, uvicorn, node 등)에 전달하려다 업스트림으로부터 유효하지 않은 응답을 받았거나 연결 자체가 끊겼다는 뜻입니다. 즉 nginx는 멀쩡한 중개자일 뿐, 진짜 범인은 거의 항상 업스트림 쪽입니다.

그래서 무작정 nginx를 재시작하는 건 위험합니다. 원인이 업스트림 OOM이나 타임아웃이라면 nginx 재시작으로는 절대 안 풀리고, 오히려 연결 상태와 로그 컨텍스트만 날아가서 원인 추적이 더 어려워집니다. 순서는 명확합니다. error.log 키워드 확인 → 원인 진단표 매칭 → 복붙 명령어로 검증 → 정확한 곳을 고친다.

502 원인 진단표 (8가지 대표 케이스)

먼저 /var/log/nginx/error.log를 열어 키워드를 잡고, 아래 표에서 바로 매칭하세요.

원인error.log 키워드확인 명령어1줄 해결책
업스트림 다운connect() failed (111: Connection refused)sudo systemctl status php8.2-fpm업스트림 프로세스 기동/재기동
connect 타임아웃upstream timed out ... while connectingcurl -v http://127.0.0.1:8000/health방화벽/네트워크·proxy_connect_timeout 점검
read 타임아웃upstream timed out ... while reading response headerjournalctl -u gunicorn -n 50proxy_read_timeout과 앱 --timeout 정렬
응답 버퍼 부족upstream sent too big headernginx -T | grep bufferproxy_buffer_size/fastcgi_buffer_size 증설
유닉스 소켓 권한connect() ... Permission deniedls -l /run/php/php-fpm.socklisten.owner/group/mode 교정
SELinux 차단connect() failed (13: Permission denied)sudo getsebool httpd_can_network_connectsetsebool -P httpd_can_network_connect 1
헬스 실패/노드 없음no live upstreams while connectingss -tlnp | grep 9000업스트림 노드 복구·proxy_next_upstream
조기 연결 종료upstream prematurely closed connectionjournalctl -u uvicorn -n 100게이트웨이 타임아웃/OOM/keepalive 점검

error.log 읽고 키워드로 원인 좁히기

표만으로 부족하면 아래 명령어를 순서대로 복붙해 원인을 좁힙니다. 각 명령어가 무엇을 확인하는지 함께 적었습니다.

Bash
# 1) 업스트림 프로세스가 살아있나? (Active: running 확인)
sudo systemctl status php8.2-fpm

# 2) 업스트림이 기대한 포트에서 실제로 리스닝 중인가?
ss -tlnp | grep 9000

# 3) nginx를 건너뛰고 업스트림을 직접 호출 — 여기서 200이면 nginx 설정 문제
curl -v http://127.0.0.1:8000/health

# 4) 유닉스 소켓 방식이면 소켓으로 직접 호출
curl --unix-socket /run/php/php-fpm.sock http://localhost/

# 5) nginx 설정 문법 검사 후 무중단 reload
sudo nginx -t && sudo systemctl reload nginx

# 6) gunicorn/uvicorn 게이트웨이 로그에서 OOM·timeout 흔적 찾기
journalctl -u gunicorn -n 50

핵심은 3번입니다. nginx를 건너뛰고 업스트림을 직접 때렸을 때 정상 응답이 오면 문제는 nginx 설정(포트·소켓·타임아웃)에 있고, 직접 호출도 실패하면 범인은 100% 업스트림입니다. 이 한 줄로 책임 소재가 절반으로 줄어듭니다.

원인별 복붙 수정 레시피

proxy_pass (gunicorn/uvicorn/node) 타임아웃·버퍼 튜닝

Nginx
# Before — 기본값이라 느린 응답·큰 헤더에서 502
location / {
    proxy_pass http://127.0.0.1:8000;
}

# After — 타임아웃과 버퍼를 현실에 맞게
location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_connect_timeout 5s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
    proxy_buffer_size 32k;
    proxy_buffers 8 16k;
    proxy_busy_buffers_size 64k;
}

upstream sent too big header가 떴다면 proxy_buffer_size를 키우는 게 정답입니다. 큰 쿠키나 JWT 헤더가 기본 버퍼를 넘기는 경우가 흔합니다.

fastcgi_pass (php-fpm) 소켓 vs 포트

Nginx
# 유닉스 소켓 방식 — 같은 호스트면 빠르지만 권한 함정 주의
fastcgi_pass unix:/run/php/php-fpm.sock;

# TCP 포트 방식 — 컨테이너/원격 분리 시 권장
fastcgi_pass 127.0.0.1:9000;

# 공통: 버퍼/타임아웃도 fastcgi_ 접두어로 따로 설정
fastcgi_read_timeout 60s;
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;

소켓 경로 오타나 포트 불일치는 connect() failed의 단골입니다. ss -tlnp | grep 9000으로 실제 리스닝 주소와 설정을 반드시 대조하세요.

SELinux와 소켓 권한 함정

RHEL/Rocky/AlmaLinux 계열은 SELinux가 기본 enforcing이라, nginx가 업스트림으로 네트워크 연결을 시도하면 차단됩니다. 증상은 connect() failed (13: Permission denied) — 포트는 열려 있는데 권한 거부가 나면 SELinux를 의심하세요.

Bash
# 현재 정책 상태 확인 (off면 이게 범인)
sudo getsebool httpd_can_network_connect

# 영구 허용 (-P 옵션이 재부팅 후에도 유지)
sudo setsebool -P httpd_can_network_connect 1

유닉스 소켓을 쓴다면 php-fpm 풀 설정의 소유자/그룹/권한을 nginx 실행 유저와 맞춰야 합니다. /etc/php/8.2/fpm/pool.d/www.conf:

INI
listen = /run/php/php-fpm.sock
listen.owner = www-data   ; nginx 실행 유저 (RHEL 계열은 nginx)
listen.group = www-data
listen.mode = 0660

컨테이너에서 볼륨으로 소켓을 공유할 때 Permission denied가 빈발하는데, 컨테이너 간 UID/GID가 다르면 같은 그룹으로 못 묶입니다. 이럴 땐 차라리 TCP 포트(127.0.0.1:9000) 방식이 마음 편합니다.

upstream prematurely closed connection — nginx만 만지면 안 풀린다

가장 헷갈리는 케이스입니다. upstream prematurely closed connection while reading response header는 nginx가 응답을 기다리는 도중 업스트림이 먼저 연결을 끊어버린 상황입니다. nginx 타임아웃만 늘려도 안 풀리는 이유가 여기 있습니다. 진짜 원인은 게이트웨이 쪽에 있습니다.

  • gunicorn/uvicorn 자체 타임아웃: gunicorn --timeout 30 기본값에 걸려 워커가 요청을 강제 종료
  • 워커 OOM: 메모리 초과로 워커가 죽으면서 연결이 끊김 (journalctl -u gunicornWorker ... was sent SIGKILL! 흔적)
  • keepalive 불일치: nginx는 연결을 재사용하려는데 업스트림이 keepalive를 닫아버림

실무에서 겪은 사례 하나. 분명 nginx proxy_read_timeout을 120초로 늘렸는데도 60초쯤이면 502가 났습니다. 알고 보니 gunicorn --timeout이 60초로 묶여 있어서, nginx가 더 기다리든 말든 gunicorn이 먼저 워커를 죽이고 있었던 거죠. 양쪽 타임아웃을 함께 보는 습관이 없으면 이 함정에서 한참을 헤맵니다.

재발 방지 체크리스트

Nginx
upstream app {
    server 127.0.0.1:8000;
    keepalive 32;            # 연결 재사용으로 부하·지연 감소
}

server {
    location / {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";   # keepalive 사용 시 필수
        proxy_read_timeout 90s;           # 아래 앱 timeout보다 살짝 크게
        proxy_next_upstream error timeout http_502;
    }
}
  • 타임아웃 정렬: gunicorn --timeout 60이면 nginx proxy_read_timeout은 그보다 크게(예: 90s). 게이트웨이가 먼저 죽지 않게.
  • 헬스체크 엔드포인트: /health를 두고 로드밸런서/모니터링이 주기적으로 호출, proxy_next_upstream으로 죽은 노드 자동 우회.
  • graceful reload 습관: 설정 변경은 sudo nginx -t && sudo systemctl reload nginx. restart 대신 reload로 무중단 적용.

502가 다시 떴을 때 따라 칠 순서만 외워두세요. error.log 키워드 확인 → 업스트림 직접 curl → 진단표 매칭 → 정확한 한 곳 수정 → reload. 추측으로 재시작하던 시절과는 복구 속도가 완전히 달라집니다.

참고: 공식 문서

이 글에서 다루는 동작·설정·에러의 1차 출처는 다음 공식 문서입니다. 버전별 옵션과 정확한 동작은 여기서 확인하세요.

자주 묻는 질문 (FAQ)

Q. nginx를 재시작해도 502가 계속 납니다. 왜죠? A. 502는 대부분 업스트림(php-fpm/gunicorn/uvicorn) 문제라 nginx 재시작으로는 안 풀립니다. curl -v http://127.0.0.1:포트/health로 업스트림을 직접 호출해 보세요. 여기서도 실패하면 범인은 100% 업스트림입니다.

Q. upstream prematurely closed connection은 어떻게 잡나요? A. nginx 타임아웃이 아니라 게이트웨이 자체 타임아웃(gunicorn --timeout)·워커 OOM·keepalive 불일치를 의심하세요. journalctl -u gunicorn -n 100에서 SIGKILL이나 timeout 흔적을 확인하고, 앱 타임아웃을 nginx proxy_read_timeout보다 작게 정렬하면 됩니다.

Q. 포트는 열려 있는데 connect() failed (13: Permission denied)가 납니다. A. RHEL/Rocky 계열의 SELinux 차단이 유력합니다. sudo getsebool httpd_can_network_connect로 확인 후 sudo setsebool -P httpd_can_network_connect 1로 허용하세요. 유닉스 소켓이면 listen.owner/group/mode도 nginx 유저와 맞춰야 합니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

초안 · AI (Content Reviewer)·검토 · Nodelog 편집자·발행 ·
관련 공식 문서NGINX 공식 문서

댓글

첫 번째 댓글을 남겨보세요.