/인프라/nginx 502 Bad Gateway 원인 7가지와 error.log 5분 진단법
인프라nginx 502Bad Gateway

nginx 502 Bad Gateway 원인 7가지와 error.log 5분 진단법

nginx 502 Bad Gateway, error.log 한 줄로 원인을 가려내세요. php-fpm·gunicorn 다운, 소켓 권한, 버퍼 부족, SELinux 차단까지 7가지 원인별 진단 명령어와 해결 설정을 5분 플로우차트로 정리했습니다.

nginx 502 Bad Gateway 원인 7가지와 error.log 5분 진단법

nginx 502 Bad Gateway 5분 진단법 — 원인 7가지 로그·명령어 총정리

502는 "업스트림이 응답을 못 줬다"는 신호다

502를 만나면 가장 먼저 헷갈리는 게 504, 403, 413과의 구분입니다. 한 문단으로 선을 긋겠습니다.

  • 502 Bad Gateway: nginx가 업스트림(php-fpm/gunicorn/node)에 연결을 시도했지만 응답이 아예 없거나, 받던 응답이 끊겼다.
  • 504 Gateway Timeout: 연결은 됐고 응답도 오는 중인데 너무 늦어서 nginx가 기다리다 끊었다.
  • 403 Forbidden: nginx 단계에서 권한/접근 규칙에 막힌 것. 업스트림까지 가지도 않음.
  • 413 Request Entity Too Large: 업로드 본문이 client_max_body_size를 초과한 것.

즉 502는 "백엔드가 죽었거나, 주소를 못 찾거나, 응답을 끝까지 못 줬다"는 뜻입니다. 그러니 추측하지 말고 지금 당장 error.log부터 보세요.

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

페이지를 한 번 새로고침하면 502가 찍히는 순간 한 줄이 흐릅니다. 그 한 줄의 문자열이 7가지 원인 중 어디인지 바로 알려줍니다.

원인별 error.log 패턴 매핑 표

먼저 이 표를 머리에 넣으세요. 로그 한 줄이면 분기가 끝납니다.

error.log 핵심 문자열원인1차 조치
connect() failed (111: Connection refused)업스트림 프로세스 다운 / 포트 오타프로세스 기동, 포트 확인
no such file or directory (소켓 경로)unix socket 경로 불일치소켓 실제 경로 확인
connect() ... Permission denied소켓 권한 문제listen.owner/group/mode
upstream prematurely closed connection업스트림 크래시 / 워커 타임아웃앱 로그·워커 설정 점검
upstream sent too big header응답 헤더 버퍼 부족proxy_buffer_size 증설
no live upstreams while connecting전 워커 다운 / 헬스체크 실패워커 수·readiness 점검

원인 1~3: 업스트림이 없거나 못 찾는 경우

(1) php-fpm/gunicorn/node 프로세스가 죽었다

로그에 connect() failed (111: Connection refused) while connecting to upstream이 보이면 십중팔구 업스트림이 안 떠 있습니다. 바로 확인합니다.

Bash
# 프로세스 상태
systemctl status php-fpm gunicorn

# 포트가 실제 LISTEN 중인지 (TCP 방식)
ss -ltnp | grep -E '9000|8000'

# nginx를 거치지 않고 업스트림에 직접 노크
curl -v http://127.0.0.1:9000/

ss 결과에 해당 포트가 없으면 프로세스가 죽은 것입니다. systemctl restart php-fpm 또는 systemctl restart gunicorn으로 살린 뒤 다시 curl로 확인하세요.

(2) proxy_pass 주소·포트 오타

프로세스는 8000번에 떠 있는데 nginx는 8080을 바라보는 경우입니다. 로그는 똑같이 Connection refused가 뜨지만, ss로는 포트가 멀쩡히 보입니다. nginx 설정의 포트와 ss 결과를 대조하세요.

Nginx
# TCP 업스트림 (gunicorn/uvicorn/node)
location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

수정 후엔 nginx -t && systemctl reload nginx. 오타 한 글자로 30분 날리는 게 가장 흔한 케이스라 항상 nginx -t로 검증부터 하세요.

(3) unix socket 권한·경로 오류

소켓 방식(php-fpm 기본)이라면 두 가지로 갈립니다.

  • no such file or directory → 경로가 다르다.
  • Permission denied → 경로는 맞는데 nginx 사용자가 소켓을 못 연다.
Bash
# 소켓 실제 위치·권한 확인
ls -l /run/php-fpm/www.sock

# 소켓에 직접 요청
curl --unix-socket /run/php-fpm/www.sock http://localhost/

php-fpm 8.x부터 소켓 기본 권한이 바뀌어 nginx 유저가 못 읽는 일이 잦습니다. 풀 설정(/etc/php-fpm.d/www.conf)에서 소유자를 맞춰주세요.

INI
listen = /run/php-fpm/www.sock
listen.owner = nginx
listen.group = nginx
listen.mode  = 0660
Nginx
location ~ \.php$ {
    fastcgi_pass unix:/run/php-fpm/www.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

저장 후 systemctl restart php-fpm. 경로가 틀렸다면 위 listen 값과 nginx의 fastcgi_pass를 글자 단위로 일치시키세요.

원인 4~5: 연결은 됐는데 응답이 끊기거나 잘리는 경우

(4) 업스트림이 응답 중 연결을 끊었다

upstream prematurely closed connection while reading response header가 핵심 문자열입니다. 여기서 504와 헷갈리면 안 됩니다.

502 vs 504 한 컷: 502 = 업스트림이 응답 도중 연결을 끊었다(끊김). 504 = 업스트림이 살아 있지만 응답이 너무 늦다(타임아웃). prematurely closed는 끊긴 것이므로 502이고, proxy_read_timeout 초과로 nginx가 먼저 끊으면 504입니다. 원인 4를 504로 오진해 타임아웃만 늘리면 영원히 안 고쳐집니다.

대개 gunicorn/uvicorn 워커가 메모리 초과(OOM)나 예외로 죽었거나, gunicorn --timeout이 너무 짧아 워커가 자기 자신을 강제 종료한 경우입니다. 앱 로그(journalctl -u gunicorn)부터 보고, 워커 타임아웃을 실제 처리 시간보다 넉넉히 잡으세요.

Bash
gunicorn app:app \
  --workers 4 \
  --timeout 60 \
  --bind 127.0.0.1:8000

(5) 헤더 버퍼 부족

upstream sent too big header while reading response header from upstream은 응답 헤더(쿠키 폭증, 긴 리다이렉트, JWT 등)가 nginx 기본 버퍼보다 클 때 납니다. 버퍼를 키워주면 끝입니다.

Nginx
location / {
    proxy_pass http://127.0.0.1:8000;

    proxy_buffer_size   16k;
    proxy_buffers       4 16k;
    proxy_busy_buffers_size 16k;
}

nginx -t && systemctl reload nginx 후 재현해 보세요.

원인 6~7: 환경·용량 문제

(6) SELinux/방화벽이 업스트림 포트를 차단

Rocky/AlmaLinux 등 RHEL 계열은 SELinux가 기본 활성화라 connect() failed (13: Permission denied)가 뜨는데, 막상 소켓 권한은 멀쩡한 경우가 있습니다. 이때는 SELinux가 nginx의 네트워크 연결 자체를 막은 것입니다.

Bash
# nginx의 아웃바운드 연결 허용 여부
getsebool -a | grep httpd
getsebool httpd_can_network_connect

# 거부 로그 추적
ausearch -m avc -ts recent

# 허용 (영구)
setsebool -P httpd_can_network_connect 1

# 방화벽도 함께 확인
firewall-cmd --list-all

httpd_can_network_connectoff라면 위 setsebool이 정답입니다.

(7) keepalive 누락·워커 부족 (no live upstreams)

no live upstreams while connecting to upstream은 nginx가 보기에 살아 있는 워커가 하나도 없다는 뜻입니다. 트래픽이 몰려 php-fpm pm.max_children나 gunicorn --workers가 동나거나, 컨테이너 환경에서 파드가 재시작 중이고 readiness probe가 없어 죽은 파드로 트래픽을 보낼 때 자주 납니다.

먼저 upstream 블록에 keepalive를 넣어 연결 폭주를 줄이세요.

Nginx
upstream backend {
    server 127.0.0.1:8000;
    keepalive 32;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

워커 수는 코어 수 기준으로 산정합니다.

  • gunicorn(동기): workers = (2 × CPU 코어) + 1
  • uvicorn/비동기: 코어 수 + --worker-class uvicorn.workers.UvicornWorker
  • php-fpm: pm.max_children = 가용 메모리 / 프로세스당 평균 메모리

쿠버네티스라면 readiness probe를 반드시 설정해 준비 안 된 파드로 트래픽이 가지 않게 하세요. 이게 컨테이너 환경 no live upstreams의 절반을 막아줍니다.

실무 한마디

제 경험상 502의 80%는 tail -f error.log + ss -ltnp 두 줄로 5분 안에 끝납니다. 정작 시간을 잡아먹는 건 "감으로 nginx 타임아웃부터 늘리는" 행동입니다. 원인 4를 504로 착각해 proxy_read_timeout만 만지면 증상은 그대로입니다. 로그 문자열을 먼저 읽고 분기하는 습관 하나가 야근을 줄여줍니다.

5분 진단 플로우차트

CODE
[502 발생] → tail -f /var/log/nginx/error.log → 어떤 문자열?
│
├─ "Connection refused (111)"
│     ├─ ss -ltnp 에 포트 없음 → 프로세스 다운 → systemctl restart
│     └─ 포트 있음 → proxy_pass 포트 오타 → 설정 수정 → nginx -t
│
├─ "no such file or directory" → 소켓 경로 불일치 → ls -l 소켓 / listen 값 일치
│
├─ "Permission denied"
│     ├─ 소켓 권한 → listen.owner/group/mode = nginx/0660
│     └─ getsebool httpd_can_network_connect = off → setsebool -P ... 1
│
├─ "upstream prematurely closed connection" → 앱 크래시/워커 타임아웃
│        (504 아님!) → journalctl -u gunicorn, --timeout/--workers 점검
│
├─ "upstream sent too big header" → proxy_buffer_size 16k; proxy_buffers 4 16k;
│
└─ "no live upstreams" → 워커 부족/readiness 미설정 → keepalive 32; + 워커 증설

재발 방지 체크리스트

  • 헬스체크: 업스트림에 /healthz 엔드포인트 + nginx/로드밸런서 헬스체크 연동
  • 모니터링: error.log의 upstream/502 키워드 알림(Loki/CloudWatch/Grafana)
  • 워커 수 산정: 코어·메모리 기준 공식으로 산정하고 부하테스트로 검증
  • K8s: readiness/liveness probe 필수 설정

자주 묻는 질문 (FAQ)

Q. 502랑 504, 어떻게 구분하나요? A. 502는 업스트림이 응답을 못 주거나 도중에 끊긴 것이고, 504는 응답이 오긴 하는데 너무 늦어 타임아웃된 것입니다. error.log에 prematurely closed면 502, timed out이면 504입니다.

Q. php-fpm 소켓을 쓰는데 Permission denied가 떠요. A. php-fpm 8.x는 소켓 기본 권한이 nginx 유저와 안 맞는 경우가 많습니다. www.conf에서 listen.owner = nginx, listen.group = nginx, listen.mode = 0660으로 맞추고 php-fpm을 재시작하세요.

Q. Rocky Linux에서만 502가 나는데 권한은 정상이에요. A. SELinux가 nginx의 네트워크 연결을 차단했을 가능성이 큽니다. getsebool httpd_can_network_connect가 off라면 setsebool -P httpd_can_network_connect 1로 해결됩니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.

작성 · Content Reviewer·검토 · 사람 편집자·발행 · 2026년 6월 16일

댓글

불러오는 중...