도커 백엔드를 재생성한 뒤 nginx가 502를 낼 때 resolver와 변수 proxy_pass로 고치기
proxy_pass에 컨테이너 이름을 그대로 쓰면 nginx는 시작할 때 찾은 IP를 계속 씁니다. 원인을 확인하는 순서, 재시작으로 급하게 푸는 법, resolver 127.0.0.11과 변수 proxy_pass로 바꾸는 설정과 바꾼 뒤 404가 나는 경우를 적었습니다.
집 미니PC에 도커 컴포즈로 백엔드 컨테이너 하나와 그 앞에 붙은 웹(nginx) 컨테이너 세 개를 올려 두고 씁니다. 백엔드 이미지를 새로 빌드해서 백엔드 컨테이너 하나만 재생성했는데, 손대지 않은 웹 세 개가 한꺼번에 502를 냈습니다. nginx가 proxy_pass http://core:8080;의 core를 시작할 때 한 번만 IP로 바꿔 두고 다시 찾지 않아서 생긴 일이었습니다. 급하면 nginx 컨테이너를 재시작하면 되고, 다시 안 생기게 하려면 resolver 127.0.0.11과 변수 proxy_pass로 바꾸면 됩니다.

백엔드는 살아 있는데 nginx만 502를 낼 때 보이는 것
이 문제는 죽은 곳이 없어서 헷갈립니다. 그때 확인한 상태는 이랬습니다.
- 백엔드 컨테이너는 정상으로 떠 있었습니다.
- nginx 컨테이너 안에 들어가서
core이름을 조회하면 백엔드 IP가 잘 나왔습니다. - 백엔드에 직접 요청을 넣으면 응답도 정상이었습니다.
- nginx를 거쳐 들어오는 요청만 502였습니다.
건드린 건 백엔드 하나인데 웹 세 개가 같이 죽었다는 점이 단서입니다. 앞단 설정은 그대로이고 뒷단을 새로 만든 것만 바뀌었으니, 새로 만드는 동안 무엇이 바뀌었는지를 보면 됩니다. 도커는 컨테이너를 새로 만들 때 네트워크 대역에서 IP를 다시 배정하기 때문에, 재생성 전후로 백엔드 IP가 달라질 수 있습니다.
원인 확인 순서
같은 증상이면 아래 순서로 보면 빠릅니다. web·core는 예시이니 자기 컴포즈 파일의 서비스 이름으로 바꿔서 씁니다.
- nginx 안에서 이름이 지금 어느 IP로 풀리는지 봅니다.
docker compose exec web getent hosts core - 백엔드 컨테이너의 실제 IP를 봅니다.
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <백엔드 컨테이너> - nginx 에러 로그에서 요청을 보낸 주소를 봅니다.
docker compose logs web에 찍힌upstream: "http://<IP>:8080/..."의 IP가 1·2번에서 본 IP와 다르면 이 문제입니다. - nginx만 재시작해 보고 502가 사라지는지 봅니다. 사라지면 확정입니다.
1번과 2번은 같은데 3번 로그의 IP만 다르면, DNS는 멀쩡하고 nginx가 다시 물어보지 않은 것입니다.
nginx가 proxy_pass 호스트 이름을 한 번만 찾는 이유
설정은 이렇게 써 두고 있었습니다.
proxy_pass http://core:8080;
core는 도커 컴포즈가 붙여 준 백엔드 서비스 이름입니다. 요청마다 이름으로 찾아갈 것처럼 보이지만, 호스트 이름을 이렇게 그대로 쓰면 nginx는 시작할 때(설정을 읽을 때) 이 이름을 IP로 바꿔 두고 그 뒤로는 다시 찾지 않습니다. 백엔드를 재생성해서 IP가 바뀌어도 nginx는 시작할 때 잡아 둔 옛 IP로 계속 보내고, 그 주소에는 아무도 없으니 502가 납니다.
급할 때: nginx 컨테이너 재시작
당장은 앞단 nginx 컨테이너를 재시작하면 됩니다.
docker compose restart web
다시 시작하면서 이름을 새로 찾기 때문에 바뀐 IP로 붙고 502가 사라집니다. 다만 백엔드를 새로 만들 때마다 같이 해야 하고, 웹이 세 개면 세 번 해야 합니다. 배포 스크립트에 넣어 둘 수는 있어도 근본 해결은 아닙니다.
다시 안 생기게: resolver 127.0.0.11과 변수 proxy_pass
nginx가 이름을 주기적으로 다시 찾게 하려면 resolver와 변수 형태의 proxy_pass를 같이 씁니다.
resolver 127.0.0.11 valid=10s;
set $upstream http://core:8080;
proxy_pass $upstream;
127.0.0.11은 도커 내장 DNS 주소입니다. 도커 네트워크 문서에 사용자 정의 네트워크의 컨테이너는 이 내장 DNS를 쓰고, 앱에 DNS 주소를 직접 적어야 하면127.0.0.11을 쓰라고 나와 있습니다. 도커 컴포즈가 만드는 기본 네트워크도 사용자 정의 네트워크라 여기에 해당합니다.valid=10s는 찾은 결과를 기억해 두는 시간입니다. 10초가 지나면 nginx가 이름을 다시 찾습니다. nginx resolver 문서에 따르면 이 값을 안 주면 DNS 응답의 TTL만큼 기억합니다.proxy_pass에 변수를 넣는 게 핵심입니다. nginx proxy_pass 문서에 따르면 변수에 들어 있는 호스트 이름은 resolver로 찾습니다.resolver만 추가하고proxy_pass를 그대로 두면 시작할 때 한 번 찾는 동작은 바뀌지 않습니다.
이렇게 바꾸면 백엔드 IP가 바뀌어도 길어야 10초 안에 nginx가 새 주소로 붙습니다.
변수 proxy_pass로 바꾼 뒤 404가 나는 경우
변수 proxy_pass는 경로 처리 규칙이 다릅니다. 이걸 모르고 바꾸면 502는 사라졌는데 404가 새로 생깁니다. nginx 문서 기준으로 나누면 이렇습니다.
| 쓰는 방식 | 백엔드로 가는 경로 |
|---|---|
| 리터럴, 경로 없이 | 요청 경로 그대로 |
| 리터럴, 경로 포함 | location에 맞은 앞부분만 적어 둔 경로로 바꿈 |
| 변수, 경로 포함 | 요청 경로 전체를 적어 둔 경로로 바꿈 |
예를 들어 location /api/ 안에 proxy_pass http://core:8080/v1/;처럼 앞부분을 바꿔 주던 설정이 있었다면, 변수로 옮기는 순간 그 바꿔 주기가 없어집니다. 백엔드가 받는 경로가 달라져서 404가 납니다. 이럴 때는 백엔드가 받아야 할 경로를 변수 값 뒤에 직접 붙여 줍니다. nginx 문서에 나온 예시는 원래 요청 경로를 $request_uri로 그대로 넘기는 방식입니다.
location /name/ {
proxy_pass http://127.0.0.1$request_uri;
}
앞부분을 바꿔야 한다면 rewrite로 경로를 먼저 고친 뒤 넘기는 방법도 있습니다. 설정을 바꾼 뒤에는 502가 사라졌는지만 보지 말고, 실제 API 경로 몇 개를 호출해서 200이 오는지까지 확인합니다.
바꾸기 전에 알아 둘 점
- 변수
proxy_pass에서는proxy_redirect default를 쓸 수 없습니다(nginx proxy_pass 문서). proxy_pass에 변수를 넣었는데resolver가 없으면 요청 때 이름을 찾을 곳이 없어서 오류가 납니다. 둘은 같이 넣습니다.- nginx는 기본으로 IPv4와 IPv6 주소를 둘 다 찾습니다. IPv6를 쓰지 않는 도커 네트워크에서 IPv6 조회 관련 오류가 로그에 보이면
resolver 127.0.0.11 valid=10s ipv6=off;처럼 끌 수 있습니다. - 시작할 때 이름을 한 번만 찾는 건 nginx만이 아닙니다. 커넥션 풀, 캐시 클라이언트, 다른 프록시도 비슷하게 동작하는 경우가 많아서, 한쪽만 다시 만들었는데 관계없는 쪽이 죽으면 그쪽이 이름을 언제 찾는지부터 봅니다.
이 서버를 어떤 기계로 꾸렸는지는 중고 Mac mini 2018을 회사 서버로 쓰는 구성에, 같은 컨테이너 환경에서 로컬과 다르게 터졌던 다른 사례는 읽기전용 파일시스템에서 AccessDeniedException이 나는 이유에 적어 두었습니다.
자주 묻는 질문
백엔드 컨테이너를 재생성했더니 앞단 nginx가 502를 뱉는 이유는 무엇인가요?
nginx 설정에 proxy_pass http://core:8080; 처럼 호스트 이름을 리터럴로 쓰면 nginx는 기동 시점에 딱 한 번 이름을 풀어 IP를 설정에 굳혀 넣습니다. 이후에는 다시 조회하지 않기 때문에, 컨테이너를 재생성해 IP가 바뀌면 nginx는 옛 IP로 계속 요청을 보내고 그 결과가 502입니다.
당장 급할 때 502를 가장 빨리 없애는 방법은 무엇인가요?
앞단 nginx 컨테이너를 재시작하면 됩니다. 재시작하는 순간 이름을 다시 풀어 새 IP로 잡기 때문에 502가 사라집니다. 다만 이건 임시 조치라, 백엔드를 재생성할 때마다 반복해야 합니다.
resolver와 변수 proxy_pass로 바꿨더니 이번엔 404가 나옵니다. 왜 그런가요?
변수 proxy_pass는 리터럴 proxy_pass와 URI 처리 규칙이 다릅니다. location의 경로가 자동으로 뒤에 붙지 않으므로, 필요한 경로를 변수 값 뒤에 직접 명시해야 합니다. 이 차이를 놓치면 502는 사라지지만 404가 새로 생깁니다.