실제 재현 노트: 같은 502라도 고칠 위치가 달랐습니다
2026년 10월 12일 별도의 로컬 Docker 시험에서 Nginx 1.30.4와 Node.js 24.21.0(Linux arm64)을 실행했습니다. 아래는 운영 장애 경험이 아니라, 비민감 예제 앱에 실제 HTTP 요청을 보내고 Nginx 오류·접근 로그와 앱 수신 로그를 대조한 결과입니다. 시험 네트워크는 외부 연결을 막았고 호스트 포트는 공개하지 않았습니다.
주소만 바꾼 세 요청과 앱이 반환한 502
앱은 별도 컨테이너의 0.0.0.0:3000에서 수신하도록 했습니다. Nginx의 연결 제한은 1초, 읽기 제한도 1초였으며 자동 다음 upstream 재시도는 껐습니다. 아래 세 proxy_pass는 실제 시험에 사용한 설정의 핵심 부분입니다.
# 실패: Nginx 컨테이너 자신에게 연결
proxy_pass http://127.0.0.1:3000/;
# 실패: 앱이 수신하지 않는 내부 포트
proxy_pass http://app:3001/;
# 성공: 같은 시험 네트워크의 앱과 실제 내부 포트
proxy_pass http://app:3000;
| 시험 조건 | HTTP | 함께 확인한 근거 |
| 127.0.0.1:3000 | 502 | connect() failed (111: Connection refused), 앱 수신 기록 없음 |
| app:3001 | 502 | 같은 연결 거절 문구, 앱 수신 기록 없음 |
| app:3000의 / | 200 | 앱 수신 기록과 완결된 응답 확인 |
| app:3000의 /status502 | 502 | 앱이 의도적으로 502를 반환, 앱 수신 기록과 응답 본문 확인 |
시험 앱의 /status502는 유효한 HTTP 502와 application-generated 502 본문을 반환하도록 직접 만들었습니다. 연결 실패 두 건과 이 앱 응답 모두 Nginx 접근 로그의 status와 upstream_status가 502였습니다. 따라서 upstream_status=502만으로 연결 실패와 앱의 502를 구분할 수 없었습니다. 실제 오류 문구, 앱 요청 수신 여부, 응답 본문을 같이 봐야 했습니다.
이 결과를 진단에 적용하는 순서
연결 거절이면 먼저 앱이 수신하는 주소·내부 포트·같은 네트워크인지를 확인합니다. 이번에는 올바른 앱 주소로 연결한 요청에서 200을 확인했으며 읽기 제한을 늘릴 필요가 없었습니다. 반대로 앱이 요청을 받고 502를 반환했다면 프록시의 포트 수정만 반복하지 말고 앱의 의존 서비스와 오류 처리로 조사 범위를 옮깁니다.
시험용 X-Lab-Origin: app 헤더도 응답 출처 대조에 사용했지만, 일반 운영 서비스에 이 헤더가 있다고 가정하지 마세요. 실제로는 민감값을 제외한 요청 식별자로 양쪽 로그를 연결하는 편이 낫습니다. 오류 페이지 재작성·재시도·여러 upstream이 있는 설정에서는 응답과 로그가 달라질 수 있으므로 이 단일 앱 시험을 모든 502 원인의 목록으로 해석하지 않습니다.
이 시험의 코드·고정 입력·설정·비식별 결과는 아래 첨부 파일로 제공합니다. Docker에서 새 시험 컨테이너와 내부 네트워크만 생성한 뒤 정리하는 코드이므로 운영 서버가 아닌 독립 개발 환경에서 내용을 먼저 확인하세요. 공식 동작 설명은 Nginx의 upstream 응답 전달 설정과 함께 확인했습니다(확인일: 2026-10-12). 기존 점검 절차는 아래에 유지했습니다.
이하 기존 점검 가이드
Nginx의 502 Bad Gateway는 브라우저 요청을 받은 Nginx가 뒤쪽 애플리케이션(upstream)에서 정상 응답을 얻지 못했다는 뜻입니다. 설정을 무작정 바꾸기 전에 오류 로그, 애플리케이션 상태, Docker 네트워크, 포트, 시간 제한 순서로 좁혀야 복구 시간을 줄일 수 있습니다.
1. Nginx 오류 로그 한 줄부터 분류한다
sudo nginx -t
sudo tail -n 100 /var/log/nginx/error.log
Nginx 공식 upstream 문서는 여러 백엔드 서버를 정의하고 실패 처리와 시간 제한을 구성하는 방법을 설명합니다. 로그의 connection refused는 주소나 포트에서 프로세스가 수신하지 않는 경우, host not found in upstream은 이름 해석 문제, upstream timed out은 연결 또는 응답 지연을 우선 의심할 수 있습니다.
로그 문구가능성 높은 원인우선 확인
| connection refused | 앱 중지·포트 불일치 | 컨테이너 상태와 listen 포트 |
| host not found | 서비스명·네트워크 문제 | Compose 서비스명과 DNS |
| upstream timed out | 앱 지연·타임아웃 | 앱 로그와 처리 시간 |
| no live upstreams | 모든 백엔드 비활성 | upstream 목록과 헬스 상태 |

2. 애플리케이션이 실제로 응답하는지 확인한다
docker compose ps
docker compose logs --tail=100 app
docker compose exec nginx curl -v http://app:3000/health
컨테이너가 Up 상태라는 사실만으로 애플리케이션 준비가 끝났다고 볼 수 없습니다. Nginx 컨테이너 안에서 서비스명과 내부 포트로 health endpoint를 호출하면 네트워크와 애플리케이션 문제를 동시에 구분할 수 있습니다. health endpoint가 없다면 가벼운 루트 경로나 상태 확인 API를 사용합니다.
3. 컨테이너 안의 localhost는 자기 자신이다
Nginx와 앱이 서로 다른 컨테이너라면 proxy_pass http://localhost:3000의 localhost는 Nginx 컨테이너를 가리킵니다. 같은 Compose 네트워크에서는 http://app:3000처럼 서비스명과 컨테이너 포트를 사용합니다. Docker Compose 공식 문서도 서비스가 기본 네트워크에서 서비스명으로 발견된다고 설명합니다.
4. 호스트 포트와 컨테이너 포트를 구분한다
ports: - "8080:3000"에서 8080은 호스트에서 접근할 공개 포트이고 3000은 컨테이너 내부 포트입니다. 같은 Compose 네트워크의 Nginx는 보통 app:3000으로 연결해야 합니다. 자세한 구조는 Docker 컨테이너 네트워크와 포트 연결에서 확인할 수 있습니다.
5. 시작 순서와 준비 상태를 구분한다
depends_on만으로 애플리케이션이 요청을 받을 준비까지 기다린다고 가정하면 초기 502가 생길 수 있습니다. 애플리케이션에 healthcheck를 정의하고, 필요하면 의존 서비스가 healthy가 된 뒤 시작하도록 구성합니다. Docker healthcheck와 자동 재시작에서 상태 점검 설계를 함께 볼 수 있습니다.
6. 타임아웃을 늘리기 전에 느린 원인을 확인한다
Nginx의 proxy_connect_timeout, proxy_read_timeout, proxy_send_timeout을 조정할 수 있지만 긴 값은 장애를 숨기고 사용자 대기 시간만 늘릴 수 있습니다. 앱의 DB 연결, 외부 API, CPU와 메모리, 이벤트 루프 정체를 먼저 측정합니다. 파일 변환이나 긴 작업은 요청 안에서 끝내기보다 작업 큐와 상태 조회 방식이 적합할 수 있습니다.
7. 복구 체크리스트와 FAQ
- nginx -t로 문법을 확인합니다.
- 오류 로그의 정확한 upstream 주소와 문구를 기록합니다.
- Nginx 컨테이너에서 upstream health URL을 직접 호출합니다.
- 서비스명, 내부 포트, 공통 네트워크를 확인합니다.
- 앱 로그와 자원 사용량을 보고 원인을 수정한 뒤 재시험합니다.
Q. Nginx를 재시작하면 해결되나요?
일시적인 DNS나 연결 상태는 바뀔 수 있지만 원인을 제거한 것은 아닙니다.
Q. 504와 502는 어떻게 다른가요?
둘 다 게이트웨이 오류지만 504는 정해진 시간 안에 upstream 응답을 받지 못한 상황을 더 직접적으로 나타냅니다.
Q. 브라우저에서만 실패하면?
Nginx 뒤 앱 호출이 성공한다면 HTTPS, Host 헤더, WebSocket 업그레이드, CORS 같은 앞단 조건을 추가로 확인합니다.
공식 자료: NGINX upstream module · NGINX proxy module · Docker Compose networking (확인일: 2026-09-22)
'소프트웨어개발' 카테고리의 다른 글
| GPT-6 Sol·Luna vs GPT-5.6 Sol·Luna 비교: 요금, 용도, 실무 선택 예시 (0) | 2026.09.23 |
|---|---|
| Docker no space left on device 해결: 이미지·로그·볼륨 안전 정리 순서 (0) | 2026.09.22 |
| 기업용 VPN과 ZTNA 차이: 원격접속 보안 솔루션 선택 기준 (0) | 2026.09.22 |