본문 바로가기

Computer Science/NetWork

502 Bad Gateway가 떴다: Nginx가 범인인 척했지만 진범은 Spring Boot였다

추천캐릭터 2026. 7. 19. 15:00
728x90

502 Bad Gateway가 떴다: Nginx가 범인인 척했지만 진범은 Spring Boot였다

어느 평화로운 오후였다.

배포도 끝났고, 테스트도 통과했고, 개발자는 자신 있게 커피를 들었다.

그리고 브라우저를 새로고침한 순간.

502 Bad Gateway
nginx

커피는 식었고 개발자의 표정도 함께 식었다.

화면에는 Nginx 이름이 큼지막하게 적혀 있다. 누가 봐도 범인은 Nginx처럼 보인다.

하지만 서버 장애의 세계에서 화면에 이름이 나온 사람은 대개 범인이 아니다.

오히려 최초 신고자일 가능성이 높다.

결론부터

Nginx의 502 Bad Gateway는 대부분 다음 상황에서 발생한다.

Nginx는 정상적으로 요청을 받았지만, 뒤에 있는 Spring Boot 애플리케이션으로부터 정상적인 응답을 받지 못했다.

즉, 사용자는 Nginx까지 도착했다.

문제는 그다음이다.

사용자
  ↓
Nginx
  ↓
Spring Boot
  ↓
MariaDB 또는 외부 API

이 구조에서 Nginx가 Spring Boot에 요청을 전달했는데 연결이 거부되거나, 엉뚱한 포트로 요청했거나, 백엔드가 비정상적인 응답을 반환하면 502가 나타날 수 있다.

Nginx 공식 문서에서도 upstream 서버를 선택할 수 없거나 정상적으로 처리하지 못한 상황에서 502 응답이 반환될 수 있다고 설명한다.

용의자 1: Spring Boot가 이미 죽어 있었다

가장 흔하고 가장 허무한 원인이다.

Nginx는 살아 있다.

EC2도 살아 있다.

하지만 정작 요청을 처리할 Spring Boot 프로세스가 없다.

먼저 애플리케이션 상태를 확인한다.

sudo systemctl status myapp

직접 JAR 파일을 실행했다면 프로세스를 확인한다.

ps -ef | grep java

포트가 실제로 열려 있는지도 확인한다.

sudo ss -lntp | grep 8080

아무것도 나오지 않는다면 사건은 거의 종결됐다.

Spring Boot는 8080 포트에서 대기 중이지 않다.

애플리케이션 로그를 확인한다.

sudo journalctl -u myapp -n 200 --no-pager

로그에서 자주 만나는 사망 원인은 다음과 같다.

Address already in use
Connection refused
Access denied for user
OutOfMemoryError
Failed to configure a DataSource

이쯤 되면 Nginx는 억울하다.

그는 문을 두드렸을 뿐이다. 집 안에 아무도 없었다.

용의자 2: Nginx가 엉뚱한 집을 찾아갔다

Spring Boot는 8080 포트에서 실행 중인데 Nginx 설정은 8081을 가리킬 수 있다.

또는 애플리케이션 포트를 변경한 뒤 Nginx 설정을 수정하지 않았을 수도 있다.

Nginx 설정을 확인한다.

sudo nginx -T

주요 부분은 proxy_pass다.

location / {
    proxy_pass http://127.0.0.1:8080;
}

Spring Boot 설정도 확인한다.

server:
  port: 8080

두 포트가 다르면 Nginx는 매일 옆집 초인종을 누르고 있었던 셈이다.

설정을 수정한 뒤에는 문법 검사부터 한다.

sudo nginx -t

정상이라면 설정을 다시 불러온다.

sudo systemctl reload nginx

무조건 재시작부터 누르지 말자.

설정 파일에 오타가 있는데 재시작하면, 멀쩡하던 Nginx까지 함께 누울 수 있다.

용의자 3: Spring Boot는 살아 있지만 대화가 불가능하다

프로세스가 존재한다고 서비스가 정상이라는 뜻은 아니다.

사람도 출근했다고 반드시 일하는 것은 아니다.

Spring Boot가 실행 중이더라도 다음과 같은 상태일 수 있다.

  • DB 커넥션 풀이 고갈됨
  • 외부 API 응답을 무한정 기다림
  • 스레드 풀이 가득 참
  • 긴 GC 정지가 발생함
  • 애플리케이션 초기화가 끝나지 않음
  • 특정 요청에서 예외가 반복됨

Nginx를 거치지 말고 Spring Boot에 직접 요청한다.

curl -v http://127.0.0.1:8080/

Actuator를 사용한다면 health 엔드포인트를 확인한다.

curl -v http://127.0.0.1:8080/actuator/health

정상 응답 예시는 다음과 같다.

{
  "status": "UP"
}

Spring Boot Actuator는 애플리케이션 상태를 확인하는 /actuator/health 엔드포인트를 제공한다.

의존성은 다음과 같이 추가한다.

implementation 'org.springframework.boot:spring-boot-starter-actuator'

노출할 엔드포인트는 최소한으로 제한한다.

management:
  endpoints:
    web:
      exposure:
        include: health,info
  endpoint:
    health:
      show-details: never

show-details: always를 인터넷에 공개하면 DB 종류나 디스크 상태 같은 운영 정보가 노출될 수 있다.

건강검진 결과를 현관문에 붙여놓는 것과 비슷하다.

502와 504는 무엇이 다를까

둘 다 Nginx와 백엔드 사이에서 자주 보이지만 의미는 조금 다르다.

502 Bad Gateway

Nginx가 백엔드로부터 사용할 수 있는 정상 응답을 받지 못한 경우다.

대표적인 원인은 다음과 같다.

  • 백엔드 포트가 닫혀 있음
  • 연결이 거부됨
  • upstream 주소가 잘못됨
  • 백엔드가 연결을 비정상적으로 종료함
  • 응답 헤더가 올바르지 않음

504 Gateway Timeout

Nginx가 백엔드에 연결했지만 제한 시간 안에 응답을 받지 못한 경우다.

대표적인 원인은 다음과 같다.

  • 느린 DB 쿼리
  • 외부 API 지연
  • 커넥션 풀 고갈
  • 무거운 파일 처리
  • 과도한 동기 작업

비유하면 이렇다.

502: 전화를 걸었는데 없는 번호거나 상대가 바로 끊었다.
504: 전화는 연결됐는데 상대가 아무 말도 하지 않는다.

Nginx 로그는 거짓말을 덜 한다

브라우저 화면만 보고 추측하지 말고 에러 로그를 확인한다.

sudo tail -n 100 /var/log/nginx/error.log

실시간으로 확인하려면 다음 명령을 사용한다.

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

자주 나타나는 메시지는 다음과 같다.

연결 거부

connect() failed (111: Connection refused) while connecting to upstream

백엔드가 실행되지 않았거나 포트가 잘못됐을 가능성이 높다.

응답을 기다리다 시간 초과

upstream timed out while reading response header from upstream

Spring Boot 내부 처리, DB 또는 외부 API가 느릴 가능성이 높다.

백엔드가 먼저 연결 종료

upstream prematurely closed connection

애플리케이션 종료, 메모리 부족, 예외 또는 네트워크 문제가 의심된다.

로그는 범인을 직접 체포하지는 않는다.

하지만 최소한 누가 어느 문으로 도망갔는지는 알려준다.

기본적인 Nginx 프록시 설정

Spring Boot 앞에 Nginx를 배치한다면 다음과 같은 구성을 사용할 수 있다.

upstream spring_app {
    server 127.0.0.1:8080;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://spring_app;
        proxy_http_version 1.1;

        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;

        proxy_connect_timeout 3s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

타임아웃을 무작정 300초, 600초로 늘리는 것은 해결책이 아니다.

느린 요청이 오래 붙잡혀 있으면 Nginx와 Spring Boot의 연결, 스레드, DB 커넥션이 함께 묶인다.

“기다리면 언젠가 오겠지”는 연애에서도 서버 운영에서도 좋은 전략이 아니다.

502 발생 시 점검 순서

장애가 발생하면 다음 순서로 확인한다.

1단계: Nginx 상태

sudo systemctl status nginx
sudo nginx -t

2단계: Spring Boot 상태

sudo systemctl status myapp
ps -ef | grep java

3단계: 포트 확인

sudo ss -lntp | grep 8080

4단계: 백엔드 직접 호출

curl -v http://127.0.0.1:8080/actuator/health

5단계: Nginx 로그 확인

sudo tail -n 100 /var/log/nginx/error.log

6단계: 애플리케이션 로그 확인

sudo journalctl -u myapp -n 200 --no-pager

이 순서만 지켜도 “일단 서버 재부팅”이라는 원시적인 의식에서 벗어날 수 있다.

장애를 줄이는 운영 설정

systemd 자동 재시작

[Service]
Restart=on-failure
RestartSec=5

Actuator health check

management:
  endpoints:
    web:
      exposure:
        include: health

배포 후 로컬 검증

curl --fail http://127.0.0.1:8080/actuator/health

로그 용량 관리

sudo journalctl --vacuum-time=14d

알림 기준 설정

다음 항목은 모니터링하는 것이 좋다.

  • HTTP 5xx 비율
  • 애플리케이션 재시작 횟수
  • JVM Heap 사용률
  • DB 커넥션 풀 사용률
  • 요청 응답 시간
  • EC2 메모리와 디스크 사용률

마무리

502 Bad Gateway가 나타났다고 해서 Nginx부터 재설치할 필요는 없다.

대부분의 사건은 다음 세 문장으로 정리된다.

  1. Nginx는 요청을 정상적으로 받았다.
  2. Nginx가 Spring Boot에 요청을 넘기려 했다.
  3. Spring Boot가 없거나, 늦거나, 이상한 대답을 했다.

그러니 502가 뜨면 브라우저를 노려보지 말고 다음 명령부터 입력하자.

curl http://127.0.0.1:8080/actuator/health

Nginx는 범인이 아닐 수 있다.

그저 뒤쪽 사무실에 아무도 없다는 사실을 사용자에게 대신 전달한 불쌍한 안내 데스크 직원일 뿐이다.

728x90