← 전체 글로 돌아가기

웹 개발

502 Bad Gateway 배포 직후에 보이면 이렇게 확인하자

배포 후 502 에러가 나는 첫 15분, 어디를 먼저 봐야 하는지 정한 흐름.

배포했는데 502 Bad Gateway가 나타난다. 이 에러는 upstream 서버(앱 서버)가 응답하지 않는다는 뜻이다. 역방향 프록시(Nginx, Traefik, Cloudflare)가 앱 서버와 통신 못 했다는 거다.

당황하지 말고 순서대로 확인하면 대부분 5분 안에 해결된다.

0단계: 배포 로그 먼저 보기

# Docker 배포 중이라면
docker ps
docker logs <container-id>

# Kubernetes
kubectl get pods
kubectl logs <pod-name>

# 또는 배포 플랫폼 대시보드 (Dokploy, Vercel, Heroku 등)

컨테이너가 제대로 시작했는가? 시작했다면 어떤 포트를 listen하나?

만약 컨테이너가 exit했다면, 로그에 에러가 있을 것이다. 환경 변수 누락, 의존성 설치 실패, 포트 바인딩 실패 등.

1단계: 포트 확인

# 컨테이너 내부에서
ss -lntp | grep LISTEN
# 또는
netstat -lntp

# 로컬 테스트
curl -v http://localhost:3000

앱이 정말 listen하고 있는가? 포트가 뭔가?

흔한 실수:

  • 앱은 포트 8080으로 listen하는데, 역방향 프록시는 3000으로 설정함
  • 앱이 0.0.0.0이 아니라 localhost만 listen해서, 다른 컨테이너나 호스트에서 접근 못함

2단계: 역방향 프록시 설정 확인

# Nginx
grep -r "upstream" /etc/nginx/
grep -r "proxy_pass" /etc/nginx/

# Traefik (Docker)
docker ps | grep traefik
docker logs traefik

# 또는 설정 파일
cat /opt/traefik/traefik.yml

프록시가 앱의 올바른 주소와 포트로 forward하나?

# Nginx 예시
upstream app {
  server localhost:3000;  # 이 주소가 맞는가?
}

server {
  listen 80;
  location / {
    proxy_pass http://app;
  }
}

만약 Docker 컨테이너라면, localhost 대신 컨테이너 이름으로 접근해야 한다:

upstream app {
  server my-app:3000;  # 컨테이너명
}

3단계: 앱이 정상 동작하는지 직접 테스트

# 앱이 떠있는 컨테이너/서버에서 직접 테스트
curl -v http://127.0.0.1:3000/health
# 또는
curl -v http://app-service:3000/

HTTP 상태 코드를 본다. 200이 나오면 앱은 멀쩡하다는 뜻이다.

만약 연결 refused라면, 앱이 정말 listen하지 않는 거다.

4단계: 네트워크 확인

# Docker 네트워크 확인
docker network ls
docker network inspect <network-name>

# 컨테이너가 같은 네트워크에 있는가?
docker inspect <app-container> | grep -A 5 'Networks'

Docker Swarm이나 Kubernetes 환경이면, 프록시와 앱이 같은 네트워크(또는 namespace)에 있어야 한다.

5단계: 헬스 체크 설정 확인

# docker-compose.yml 또는 k8s manifest
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 40s

헬스 체크가 실패하면 컨테이너를 재시작한다. 배포 직후 앱이 완전히 시작되기 전에 헬스 체크가 실패해서 502가 나올 수 있다.

start_period를 충분히 길게 설정하면, 앱이 시작될 시간을 준다.

6단계: 환경 변수와 의존성

# 필요한 환경 변수가 모두 있는가?
docker exec <container> env | sort

# 데이터베이스, 캐시 등 의존성이 연결됐는가?
docker exec <container> curl http://postgres:5432  # DB 포트

DB 연결이 실패하면, 앱이 시작되지 않거나 준비 중(not ready) 상태가 된다.

7단계: 앱의 startup 지연

Node.js Express, Python Flask, Go, Rust 등 언어별로 시작 시간이 다르다.

// Node.js 예시
const app = express()

// 이 부분에서 시간이 걸릴 수 있음
const db = await connectDatabase()
const cache = await initRedis()

app.listen(3000, () => {
  console.log('Server running')
})

만약 연결에 30초 이상 걸리면, 역방향 프록시의 타임아웃 설정을 봐야 한다.

proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

응급 조치

# 이전 버전으로 빠르게 롤백
git revert <commit>
git push
# 또는 배포 플랫폼에서 이전 배포 버전 선택

# 시간을 벌면서 원인 파악

배포 직후 502가 뜨면 체크할 순서

  1. 앱 로그 확인 (컨테이너 시작됐나)
  2. 포트 확인 (앱이 listen하나)
  3. 프록시 설정 (프록시가 올바른 포트로 forward하나)
  4. 헬스 체크 (start_period가 충분한가)
  5. 환경 변수 (필수 설정이 있나)
  6. 롤백 (시간이 오래 걸리면 이전 버전으로)

이 순서대로 체크하면 5분 안에 원인을 찾을 수 있다.