슬래시 하나가 백엔드가 받는 경로를 바꾼다.
location /api/ {
proxy_pass http://backend; # URI 없음 → 원래 경로를 그대로
# /api/orders → http://backend/api/orders
}
location /api/ {
proxy_pass http://backend/; # URI 있음(/) → location 부분을 '대체'
# /api/orders → http://backend/orders
}
- proxy_pass 에 경로가 있으면 — location 접두사를 그 경로로 바꾼다
- proxy_pass 에 경로가 없으면 — 요청 URI 를 그대로 넘긴다
"백엔드에서 404 가 난다" 의 절반이 이 슬래시다 로그에서 업스트림이 실제로 받은 경로를 확인하면 바로 보인다
경로를 바꿔 붙이는 예
location /v1/ {
proxy_pass http://backend/api/v2/;
# /v1/orders → http://backend/api/v2/orders
}
버전 경로를 앞단에서 갈아 끼우는 흔한 패턴이다. 애플리케이션을 건드리지 않고 URL 을 바꿀 수 있다.
정규식 location 에서는 다르다
정규식 location 에서는 proxy_pass 에 URI 를 쓸 수 없다
- location ~ ^/api/(.*)$ {
- proxy_pass http://backend/$1; # 변수로는 가능
- }
그리고 proxy_pass 에 '변수' 가 들어가면 동작이 달라진다
-
시작 시점이 아니라 요청마다 이름을 해석한다
-
resolver 지시자가 필요하다 (없으면 기동은 되고 요청에서 실패)
-
upstream 블록의 keepalive·분배 방식이 적용되지 않는다
-
변수 proxy_pass 는 '동적 라우팅이 꼭 필요할 때만' 쓴다
함께 가야 하는 헤더
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_set_header 를 한 블록에서 '하나라도' 쓰면 상위 블록에서 상속받던 것이 전부 사라진다
- add_header 와 같은 성질이다
- 공통 헤더는 별도 파일로 만들어 include 하는 편이 안전하다
리다이렉트가 내부 주소를 노출할 때
백엔드가 Location: http://backend:8080/... 를 돌려주면 클라이언트에게 내부 주소가 그대로 나간다
-
proxy_redirect default; — # 기본. proxy_pass 기준으로 고쳐 준다 proxy_redirect http://backend/ /; # 명시적으로 지정할 수도 있다
-
애초에 Host 헤더를 제대로 넘기면 백엔드가 올바른 URL 을 만든다
-
근본 대응은 헤더 쪽이다
업스트림 이름 해석 시점
-
proxy_pass http://backend; — # upstream 블록 이름 — 시작 시 해석 proxy_pass http://api.internal; # 도메인 — 시작 시 한 번만 해석한다 ← 함정
-
두 번째는 기동 시점의 IP 를 계속 쓴다
-
컨테이너·오토스케일링으로 IP 가 바뀌면 죽은 IP 로 계속 보낸다
-
502 가 나는데 재시작하면 낫는 전형적 증상
대응
-
resolver 10.0.0.2 valid=30s;
-
set $upstream http://api.internal;
-
proxy_pass $upstream; # 변수를 쓰면 요청마다 해석한다
-
단 변수 proxy_pass 는 upstream 블록의 keepalive·분배가 적용되지 않는다
-
가능하면 upstream 블록에 IP 를 넣고 배포로 갱신하는 편이 안정적이다
원본 URI 를 그대로 넘겨야 할 때
proxy_pass 는 정규화된 URI 를 넘긴다 (//, ../ 등이 정리된다) 서명 검증처럼 원문이 필요하면 어긋난다
- proxy_pass http://backend$request_uri; # 원본을 그대로
다만 이 형태는 변수를 쓰는 것이므로 위의 해석 시점 이슈가 따라온다
면접 함정
- ❌ "proxy_pass 끝의 슬래시는 스타일 문제" → 경로 전달 방식이 완전히 달라진다.
- ❌ "변수로 써도 똑같다" → resolver가 필요하고 upstream 설정이 적용되지 않는다.