이름에 redirect라는 단어가 들어 있어서 VPN 뒤의 백엔드에 닿지 않을 때 location 블록에 붙여 넣는다. 아무것도 바뀌지 않고, 뻔한 결론은 이 지시어가 고장 났거나 다른 문법이 필요하다는 것이다. 실제로 일어난 일은 더 단순하고 덜 멋지며, proxy_redirect는 이미 돌아온 응답에 작동하고 이 배포에서는 어떤 요청도 거기까지 가지 못했다.

하나의 요청, 두 지시어, 두 순간

리버스 프록시를 지나는 HTTP 요청은 여러 판단을 순서대로 거치고, 두 지시어는 그 순서의 양 끝에 있다. 하나는 요청이 어디로 갈지 고른다. 다른 하나는 돌아오는 응답의 특정 헤더를 편집한다.

DirectiveWhen it actsWhat it can change
proxy_passBefore any bytes leave the proxyWhich upstream receives the request
proxy_redirectAfter the upstream respondsMatching Location and Refresh text

그 순서가 답의 전부다. 첫 줄이 실패해서 둘째 줄이 절대 실행되지 않는다면, 둘째 지시어는 잘못 설정된 것이 아니라 그저 도달할 수 없는 것이다. 둘째 줄을 아무리 고쳐 써도 그것이 편집해야 했던 응답은 생기지 않는다. 첫 줄에서 바깥으로 진단하면, 혼란스러운 동작은 빠진 경로나 거부된 연결로 정리된다.

업스트림 선택은 proxy_pass의 몫

NGINX가 공개 진입 호스트에서 실행되고 자체 VPN을 통해 10.40.0.20:8080의 애플리케이션에 닿는다고 하자. 그 애플리케이션을 고르는 조각은 기존 server 블록 안에서 이렇게 생겼다:

location / {
    proxy_pass http://10.40.0.20:8080;
}

실제 스킴과 주소, 포트를 사용하고, 이것을 배포가 아니라 조각으로 다뤄야 한다. 진입 호스트는 VPN 경로와 허용된 전달 경로, 그리고 그 서비스로 향하는 정상적인 반환 트래픽도 유지해야 하는데, 서류상 주소에 닿는 프록시도 여전히 커널의 동의가 필요하기 때문이다.

proxy 모듈 문서는 업스트림 선택을 응답 헤더 재작성과 분리해 두고, URI를 포함한 proxy_pass 값이 일치하는 location 접두사가 치환되는 방식을 바꾼다고 언급한다. 애플리케이션이 기대하는 URL을 확인하지 않고 경로 구성 요소를 더하거나 빼는 것은 라우팅 실패처럼 보이는 404를 만드는 확실한 방법이다.

proxy_redirect는 사설 주소를 흘리는 응답을 고친다

트래픽이 흐르기 시작하면 백엔드가 자신의 사설 주소를 지목하는 Location 헤더로 응답할 수 있다. 그 헤더를 따르는 브라우저는 공개 진입 지점을 떠나 해석할 수 없는 무언가에 닿으려 한다. 의도적으로 설정된 응답 재작성은 사설 URL을 공개 URL에 매핑한다:

proxy_redirect http://10.40.0.20:8080/ https://app.example.com/;

양쪽을 실제 값으로 바꾼다. 범위는 좁아서 일치하는 Location 및 Refresh 헤더 텍스트는 재작성되지만, HTML이나 JavaScript, 내려받은 파일에 박힌 URL은 그렇지 않다.

애플리케이션이 곳곳에서 절대 링크를 만든다면, 오래 가는 해결책은 그 애플리케이션 자체의 외부 URL 설정이지 프록시에 재작성 규칙을 점점 더 쌓는 것이 아니다. 출력이 맞아 보일 때까지 규칙을 더하면 아무도 시험하지 않은 첫 페이지에서 무너지는 설정이 된다. 애플리케이션의 올바른 설정 하나가 그 모음을 통째로 대체한다.

헤더 재작성이 고칠 수 없는 것

인접한 여러 문제가 리다이렉트 문제처럼 보이지만 아니다. 업스트림으로 보내는 Host 헤더는 백엔드의 가상 호스트와 맞아야 할 수 있다. TLS 업스트림은 특정 서버 이름과 인증서 검증을 요구할 수 있고, 응답 헤더 재작성은 둘 다에 도움이 되지 않는다.

쿠키 범위와 도메인 속성은 애플리케이션과 그 설정에 속한다. 프록시는 하루 종일 헤더를 버리거나 재작성해도 두 번째 요청에서 실패하는 세션을 만들어 내는데, 쿠키가 브라우저가 한 번도 방문하지 않은 호스트로 발급되었기 때문이다. 그 실패는 간헐적으로 보이기에 티켓 대기열에서 오래 살아남는다. 쿠키 도메인을 읽으면 보통 일 분 안에 조사가 끝난다.

인증과 백엔드 선택도 의도한 애플리케이션으로 제한해야 한다. 공개 리버스 프록시는 서버의 VPN을 통해 애플리케이션 하나만 노출해야 하며, 그 호스트가 닿을 수 있는 모든 사설 주소로 통하는 범용 게이트웨이가 되어서는 안 된다. 요청자가 원하는 것을 무엇이든 전달하는 일반 location 하나가 노출된 서비스를 사설 네트워크의 지도로 바꾼다. 업스트림을 분명히 이름 붙이고 그대로 두어라.

요청이 지나가는 순서대로 시험한다

세 가지를 차례로 확인한다: NGINX가 의도한 location을 골랐는지, 백엔드로 요청을 완료했는지, 브라우저가 이후의 리다이렉트를 따라 하위 리소스를 불러왔는지. 각 단계에는 저마다의 로그 줄이 있고, 순서대로 읽으면 모호한 502가 구체적인 발견이 된다.

필요한 구분은 실패한 업스트림 연결과 도달할 수 없는 사설 이름으로 보내진 브라우저 사이의 차이다. 전자는 프록시 오류 로그에 나타나고, 후자는 끝내 도착하지 않는 요청으로 나타나며 브라우저는 해석할 수 없는 호스트를 탓한다. 서로 다른 두 문제, 서로 다른 두 파일이며, proxy_redirect가 관련되는 것은 둘 중 하나뿐이다.

관련 글