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부터 보세요.
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이 보이면 십중팔구 업스트림이 안 떠 있습니다. 바로 확인합니다.
# 프로세스 상태
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 결과를 대조하세요.
# 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 사용자가 소켓을 못 연다.
# 소켓 실제 위치·권한 확인
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)에서 소유자를 맞춰주세요.
listen = /run/php-fpm/www.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660location ~ \.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)부터 보고, 워커 타임아웃을 실제 처리 시간보다 넉넉히 잡으세요.
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 기본 버퍼보다 클 때 납니다. 버퍼를 키워주면 끝입니다.
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의 네트워크 연결 자체를 막은 것입니다.
# 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-allhttpd_can_network_connect가 off라면 위 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를 넣어 연결 폭주를 줄이세요.
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분 진단 플로우차트
[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로 해결됩니다.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...