본문 바로가기
삵

nginx에서 내부 ALB로 proxy_pass 하면 며칠 뒤부터 504가 납니다

조회 3 · 댓글 1
케케이시88작성자2026년 9월 23일

Ubuntu 22.04 EC2에서 nginx 1.18.0(apt 기본 패키지)을 앞단에 두고 있습니다. 뒤쪽 API 서버들은 internal ALB 뒤에 있고, nginx에서는 ALB의 DNS 이름으로 바로 proxy_pass 하고 있습니다.

location /api/ {
    proxy_pass http://internal-api-alb-123456789.ap-northeast-2.elb.amazonaws.com;
    proxy_connect_timeout 5s;
    proxy_read_timeout 60s;
    proxy_set_header Host $host;
}

nginx를 재시작하고 나서 며칠 동안은 문제가 없습니다. 그런데 어느 시점부터 /api/ 요청 일부가 504로 떨어지기 시작하고, 에러 로그에는 아래 내용이 계속 쌓입니다.

2026/09/21 03:14:52 [error] 1123#1123: *884211 upstream timed out (110: Connection timed out) while connecting to upstream, client: 10.0.1.25, server: example.internal, request: "GET /api/v1/orders HTTP/1.1", upstream: "http://10.0.12.87:80/api/v1/orders", host: "example.internal"

로그에 찍힌 10.0.12.87을 확인해 보니 지금 ALB 이름을 조회하면 나오는 IP 목록에는 없습니다.

$ dig +short internal-api-alb-123456789.ap-northeast-2.elb.amazonaws.com
10.0.11.42
10.0.13.105

같은 서버에서 curl로 ALB DNS 이름을 직접 호출하면 정상 응답이 오고, sudo systemctl restart nginx를 하면 504가 바로 사라집니다. 지금은 증상이 보이면 재시작하는 식으로 버티고 있습니다.

지금까지 해본 것은 proxy_connect_timeout을 5초에서 15초로 늘려본 것과 ALB 대상 그룹 헬스체크 확인입니다. 타임아웃을 늘리니 504까지 걸리는 시간만 길어졌고, 대상 그룹은 모두 healthy 상태였습니다. /etc/resolv.conf는 VPC 기본 리졸버(127.0.0.53 stub)를 쓰고 있습니다.

OS는 이미 바뀐 IP를 알고 있는데 nginx만 예전 IP로 계속 붙으려는 이유가 무엇인지 궁금합니다. 그리고 재시작 없이 nginx가 바뀐 ALB IP를 따라가게 하려면 설정을 어떻게 잡아야 하는지 조언 부탁드립니다.

나나크나로2026년 9월 24일

증상만 보면 애플리케이션이 아니라 nginx의 DNS 처리 방식 문제로 보입니다. 정확히는 proxy_pass에 변수 없이 도메인 이름을 그대로 적으면 nginx가 그 이름을 설정 로드 시점(기동/reload)에 한 번만 resolve 하고, 그 IP를 계속 고정으로 씁니다. DNS TTL을 따라 재조회하지 않습니다. 그래서 며칠 뒤 ALB가 스케일링이나 노드 교체로 뒤쪽 IP를 바꾸면, 옛 IP(10.0.12.87)는 이미 빠졌는데 nginx는 그 IP로만 붙으려다 connect 단계에서 타임아웃, 즉 504로 떨어집니다. 에러가 "while connecting to upstream"이고 upstream이 dig 결과에 없는 IP인 점이 이 시나리오와 정확히 맞습니다.

OS(systemd-resolved stub)는 매번 새로 조회하니 curl은 정상이고, restart 하면 nginx가 그 순간의 IP로 다시 resolve 하니 바로 사라지는 것도 같은 이유입니다. proxy_connect_timeout을 늘려도 붙을 대상이 없는 IP라 504까지 걸리는 시간만 늘어난 것이고, 대상 그룹이 healthy한 것도 당연합니다. ALB 입장에선 정상이니까요.

nginx 공식 문서에도 명시돼 있습니다. proxy_pass 값에 변수가 들어가면 그때는 런타임에 resolver로 이름을 다시 조회하고, resolver의 valid 파라미터로 캐시 시간을 정할 수 있습니다. 반대로 변수가 없으면 설정 로드 때 확정됩니다.

해결은 두 가지를 같이 잡아야 합니다. (1) resolver 지정, (2) proxy_pass를 변수로 바꿔서 매 요청 시 재조회되게 만들기.

# http {} 또는 server {} 블록에 resolver 추가
# 169.254.169.253 은 VPC 기본 리졸버(Amazon DNS)
resolver 169.254.169.253 valid=10s ipv6=off;

location /api/ {
    set $api_alb internal-api-alb-123456789.ap-northeast-2.elb.amazonaws.com;
    proxy_pass http://$api_alb$request_uri;

    proxy_connect_timeout 5s;
    proxy_read_timeout 60s;
    proxy_set_header Host $host;
}

주의할 점이 하나 있습니다. proxy_pass에 변수를 쓰면 원래 요청 URI가 자동으로 전달되지 않습니다. 그래서 위처럼 $request_uri를 붙여야 기존(변수 없을 때)과 동일하게 /api/v1/orders 가 그대로 백엔드로 넘어갑니다. 이걸 빠뜨리면 경로가 깨지니 꼭 확인하세요.

resolver 주소는 127.0.0.53 stub을 그대로 쓰기보다 169.254.169.253(또는 VPC CIDR 시작주소+2, 예: 10.0.0.2)을 직접 지정하는 편을 권합니다. valid=10s는 TTL과 무관하게 최대 10초마다 재조회하라는 뜻이라, ALB IP가 바뀌어도 최대 10초 안에 따라갑니다. 값은 환경에 맞게 조정하시면 됩니다.

참고로 upstream 블록의 server에 resolve 파라미터를 붙이는 방식도 있지만 그건 NGINX Plus(상용) 기능이라, apt 기본 오픈소스 nginx 1.18.0에서는 위의 변수+resolver 방식이 정석입니다.

적용 후 확인은 이렇게 하시면 됩니다.

sudo nginx -t && sudo systemctl reload nginx

# ALB IP가 바뀌는 시점 전후로 에러 로그에 옛 IP로의
# connect timed out 이 더 안 쌓이는지 관찰
sudo tail -f /var/log/nginx/error.log

# 실제로 어느 IP로 붙는지 캡처해서 dig 결과와 대조
sudo tcpdump -nn -i any 'tcp and (port 80) and dst net 10.0.0.0/16'

정리하면 지금 설정은 nginx가 최초 1회 resolve 한 IP에 고착돼 있는 상태라, 변수+resolver로 바꾸시면 재시작 없이도 바뀐 ALB IP를 따라가게 됩니다.

관련 공식 문서는 아래를 확인해 보세요. proxy_pass의 변수 사용 시 동작과 resolver의 valid 파라미터 설명이 나와 있습니다.

🔗 http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass

🔗 http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver

로그인 후 답글을 남길 수 있습니다.