이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Path rewrite, trailing slash, Host header가 백엔드 routing을 어떻게 바꾸는가?
- health check는 성공하는데 실제 API만 404/502가 나는 이유를 어떻게 찾는가?
- upstream DNS, protocol, context path, URI normalization을 인프라 팀과 어떻게 맞추는가?
개요
Path rewrite와 upstream 설정은 외부 API URL을 내부 서비스 URL로 연결하는 규칙이다. 작은 slash 하나가 upstream으로 전달되는 path를 바꾸고, Host header 하나가 backend virtual host, OAuth redirect, absolute URL 생성을 바꾼다. 그래서 rewrite는 인프라 설정이면서 동시에 API 라우팅 코드다.
- 외부
/api/orders가 내부/orders로 갈 수도 있고 그대로/api/orders로 갈 수도 있다. proxy_pass뒤 slash 유무는 NGINX에서 URI 처리 결과를 바꾼다.- upstream protocol이 HTTP인지 HTTPS인지, SNI/Host가 무엇인지 확인해야 한다.
- health check path와 실제 API path가 다른 location을 탈 수 있다.
원리
NGINX 예시에서 차이를 보자.
location /api/ {
proxy_pass http://app_upstream/;
}이 경우 /api/orders가 upstream에는 /orders로 갈 수 있다.
location /api/ {
proxy_pass http://app_upstream;
}이 경우 /api/orders가 upstream에도 /api/orders로 전달될 수 있다. 실제 동작은 location과 proxy_pass URI 조합을 정확히 확인해야 한다.
흔한 장애
| 증상 | 가능 원인 |
|---|---|
| proxy 404 | rewrite 결과가 의도와 다름 |
| app 404 | app이 받은 context path가 다름 |
| 502 | upstream host/port/protocol/DNS 오류 |
| redirect가 내부 host로 감 | Host/X-Forwarded-Host 설정 오류 |
| health check는 성공, API 실패 | health location과 API location 설정 차이 |
| 특정 path만 실패 | URI encoding, trailing slash, regex location 우선순위 |
Upstream 설정
upstream은 단순 host 목록이 아니다. keepalive, DNS resolve, fail timeout, protocol, TLS, health가 모두 연결된다.
resolver 10.0.0.2 valid=30s ipv6=off;
upstream app_upstream {
server app-1.internal:8080;
server app-2.internal:8080;
keepalive 64;
}
location /api/ {
proxy_pass http://app_upstream;
proxy_set_header Host $host;
}Kubernetes나 service discovery 환경에서는 upstream IP가 바뀐다. NGINX가 DNS를 언제 다시 resolve하는지, ingress/controller가 endpoint 변화를 어떻게 반영하는지 알아야 한다.
코드와 명령으로 확인하기
외부 경로와 앱이 받은 경로를 비교한다. 임시 echo endpoint가 있으면 가장 빠르다.
curl -i https://api.example.com/api/debug/echo \
-H 'X-Request-ID: rewrite-test-001'echo 응답 예시다.
{
"method": "GET",
"path": "/api/debug/echo",
"host": "api.example.com",
"xForwardedHost": "api.example.com",
"xForwardedProto": "https"
}upstream 연결 문제는 proxy host에서 확인한다.
curl -i http://app_upstream/api/health
dig app-1.internal운영에서는 내부 host 접근 권한이 제한될 수 있으므로 인프라 팀과 같이 실행하거나 runbook에 대체 명령을 둔다.
URI Encoding과 Normalization
proxy와 app이 path를 다르게 normalize하면 보안/라우팅 문제가 생긴다.
%2F를 slash로 풀지 여부- double slash
// ..path traversal normalize- trailing slash redirect
- case sensitivity
WAF, gateway, proxy, app framework가 서로 다른 순서로 decode하면 “proxy는 허용했는데 앱은 다른 path로 해석”하는 문제가 생길 수 있다.
인프라 협업 포인트
- 외부 path, rewrite 후 path, upstream host/port/protocol, app context path를 한 표로 맞춘다.
- health check path와 실제 user API path가 같은 upstream 정책을 타는지 확인한다.
- Host header를 원래 host로 전달할지 upstream service host로 바꿀지 정한다.
- path rewrite 변경은 OpenAPI 문서, frontend base URL, OAuth callback, CORS origin과 함께 검증한다.
실전 팁
- rewrite는 테스트 가능한 계약이다. 배포 전 smoke test에 대표 path, trailing slash, query string, encoded path를 포함한다.
- app에
/debug/echo같은 내부 전용 endpoint가 있으면 proxy 문제를 빨리 좁힐 수 있다. 외부 공개는 제한한다. - 404가 proxy에서 난 것인지 app에서 난 것인지 error page와 log로 구분한다.
- upstream DNS 변경이 잦은 환경에서는 resolver/cache/keepalive가 stale endpoint를 붙잡지 않는지 확인한다.
- 개인 프로젝트에서도
/apiprefix를 frontend, proxy, app router 중 어디에서 제거하는지 하나로 정해 두면 헷갈림이 줄어든다.
위험 신호!
proxy_passslash 의미를 모른 채 설정을 복사한다.- app이 생성한 absolute redirect URL이 내부 upstream host를 노출한다.
- health check는
/health로 통과하지만 실제 API는 다른 rewrite rule을 탄다. - encoded path와 normalization을 보안 테스트에 포함하지 않는다.
확인 질문
확인 질문
proxy_pass의 trailing slash가 위험한 이유는 무엇인가?
- upstream으로 전달되는 URI가 달라져 prefix가 빠지거나 중복될 수 있기 때문이다.
- health check가 성공해도 실제 API가 실패할 수 있는 이유는 무엇인가?
- health check와 실제 API가 다른 location, rewrite, auth, upstream 설정을 탈 수 있기 때문이다.
- path rewrite 장애에서 가장 좋은 증거는 무엇인가?
- 외부 original URI, proxy가 전달한 upstream URI, 앱이 실제로 받은 path를 같은 request id로 비교한 증거다.