Nginx 도커 발행시 502에러가 발생하는 이유
nginx를 도커 컨테이너로 띄워 다른 컨테이너 앞에 두면, 앱 컨테이너를 다시 빌드한 직후 502 Bad Gateway가 날 수 있습니다. 앱은 정상인데 nginx가 컨테이너의 예전 IP로 연결을 시도하기 때문입니다.

제 서버에서는 docker compose up -d --build로 앱을 다시 띄울 때마다 Cloudflare의 502 화면이 떴습니다. 앱 로그는 깨끗했고, 서버 안에서 앱에 직접 요청하면 정상 응답이 왔습니다. 이 글은 그 원인과, nginx 설정 세 줄로 다시는 생기지 않게 한 방법을 정리한 것입니다.
원인: nginx는 컨테이너 주소를 시작할 때 한 번만 찾는다
nginx 설정에 proxy_pass http://앱컨테이너이름:8080;처럼 컨테이너 이름을 적으면, nginx는 설정을 읽을 때(시작하거나 reload할 때) 그 이름을 IP로 한 번 바꾼 뒤 계속 그 IP를 씁니다. 이름을 다시 찾아보지 않습니다.
문제는 도커가 컨테이너를 새로 만들 때마다 IP를 다시 배정한다는 점입니다. docker compose up -d --build는 기존 컨테이너를 지우고 새 컨테이너를 만들기 때문에, 앱의 IP가 바뀔 수 있습니다. 그러면 nginx는 이미 없는 예전 IP로 요청을 보내고, 연결에 실패해 502 Bad Gateway를 돌려줍니다.
그동안 멀쩡했던 것은 운이 좋았기 때문입니다. 새 컨테이너가 우연히 예전과 같은 IP를 다시 받으면 문제가 드러나지 않습니다. 제 경우에는 같은 네트워크의 다른 컨테이너들을 정리한 뒤부터 IP 배정 순서가 달라져 매번 502가 났습니다.
확인 방법: 앱에 직접 물어보기
502의 원인이 앱인지 nginx인지는 서버 안에서 앱에 직접 요청해 보면 바로 갈립니다. 앱이 포트를 127.0.0.1:8080으로 열어 두었다면 이렇게 확인합니다.
# 1) 앱 컨테이너가 떠 있는지
sudo docker ps --format '{{.Names}}\t{{.Status}}'
# 2) nginx를 거치지 않고 앱에 직접 요청
curl -s -o /dev/null -w '앱 직접 응답: %{http_code}\n' http://127.0.0.1:8080/
# 3) 앱의 지금 IP와 nginx가 연결하려는 IP 비교
sudo docker inspect 앱컨테이너이름 --format '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}'
sudo docker logs --tail 20 nginx 2>&1 | grep -i upstream
결과는 이렇게 읽습니다.
| 앱 직접 응답 | 뜻 |
|---|---|
| 200, 401 등 숫자 | 앱은 정상입니다. nginx가 예전 IP를 붙잡고 있는 것입니다. |
| 000 | 앱이 응답하지 않습니다. 앱 로그(docker logs)부터 확인합니다. |
앱이 정상인데 nginx 로그의 upstream 주소가 3번에서 본 IP와 다르면 이 글의 경우가 맞습니다.
임시 해결: nginx reload
nginx에게 설정을 다시 읽히면 컨테이너 이름을 새로 찾기 때문에 바로 복구됩니다. 접속 중인 사용자를 끊지 않고 적용됩니다.
sudo docker exec nginx nginx -s reload
다만 다음 빌드 때 또 같은 일이 생깁니다. 당장은 빌드 명령 뒤에 reload를 붙여 쓰면 됩니다.
sudo docker compose up -d --build && sudo docker exec nginx nginx -s reload
근본 해결: 도커 내부 DNS에 주기적으로 다시 묻게 하기
nginx가 컨테이너 이름을 정해진 시간마다 다시 찾게 하면, 컨테이너를 새로 만들어도 reload 없이 따라갑니다. 필요한 것은 설정 세 줄입니다.
바꾸기 전:
location / {
proxy_pass http://app_container:8080;
...
}
바꾼 뒤:
location / {
resolver 127.0.0.11 valid=10s;
set $app_upstream http://app_container:8080;
proxy_pass $app_upstream;
...
}
resolver 127.0.0.11: 도커가 모든 컨테이너 안에 제공하는 내부 DNS 서버 주소입니다. 컨테이너 이름을 지금의 IP로 알려 줍니다.valid=10s: 찾은 IP를 10초 동안만 기억하고, 그 뒤에는 다시 묻습니다.set+ 변수로 쓴proxy_pass: 이 부분이 핵심입니다.proxy_pass에 주소를 직접 적으면 nginx는 resolver를 두어도 시작할 때 한 번만 찾습니다. 주소를 변수에 담아 넘겨야 요청이 올 때마다 resolver로 찾습니다.
적용은 설정 검사를 통과할 때만 reload하도록 합니다.
sudo docker exec nginx nginx -t && sudo docker exec nginx nginx -s reload
이후에는 평소처럼 빌드만 하면 되고, 새 컨테이너가 뜬 뒤 길어야 10초 안에 nginx가 새 IP로 넘어갑니다.
주의할 점
- 사용자 정의 네트워크에서만 됩니다.
127.0.0.11DNS는 docker compose가 만드는 네트워크나docker network create로 만든 네트워크에 붙은 컨테이너에서만 동작합니다. 도커 기본bridge네트워크에서는 컨테이너 이름으로 찾을 수 없습니다. nginx와 앱이 같은 네트워크에 붙어 있는지 먼저 확인하세요. - 변수 proxy_pass는 주소 뒤 경로를 다르게 다룹니다.
proxy_pass http://app:8080/api/;처럼 주소 뒤에 경로를 붙여 쓰던 설정은 변수로 바꾸면 요청 경로가 다르게 전달될 수 있습니다. 이 글처럼http://이름:포트까지만 쓰는 경우는 차이가 없습니다. - 잠깐의 502는 남을 수 있습니다. 새 컨테이너가 뜨는 몇 초 동안과, nginx가 기억한 IP의 유효 시간(
valid=10s)이 지나기 전까지는 502가 날 수 있습니다. 계속 이어지지는 않습니다. - 다른 방법도 있습니다. 앱 컨테이너에 고정 IP를 주는 방법(
ipv4_address)이나, 앱 포트를 서버에 열고 nginx가host.docker.internal같은 호스트 주소로 연결하는 방법도 있습니다. 다만 설정이 늘고 관리할 것이 많아져서, 컨테이너가 여럿이라면 resolver 방식이 가장 간단합니다. - Cloudflare를 쓴다면 502와 526을 구분하세요. 502는 nginx 뒤의 앱에 연결하지 못한 것이고, 526은 Cloudflare가 서버의 SSL 인증서를 믿지 못한 것입니다. 원인이 전혀 다릅니다.
한눈에 보기
# 1) 원인 확인: 앱 직접 응답이 숫자면 앱은 정상, nginx 문제
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/
# 2) 당장 복구
sudo docker exec nginx nginx -s reload
# 3) 근본 해결: nginx 설정의 proxy_pass를 아래 세 줄로 바꾼 뒤
# resolver 127.0.0.11 valid=10s;
# set $app_upstream http://app_container:8080;
# proxy_pass $app_upstream;
sudo docker exec nginx nginx -t && sudo docker exec nginx nginx -s reload