Summary
본 포스트에서는 평소 헷갈리고 난해하게 느껴졌던 두 가지 개념에 대해 학습한 내용을 정리해봤습니다.
trust proxy설정- e.g. Express 에서의 설정
X‑Forwarded‑ProtoHeader
이 두 개념이 왜 필요한지,
nginx 등 리버스 프록시(Reverse Proxy) 구조를 사용하는 서버 구성에서 어떤 역할을 하는지, 그리고 실제로 어떻게 설정해야 하는지에 대해 학습하고 이해한 내용을 정리해봤습니다!1. 기본 개념 정리
1.1 리버스 프록시 서버(Reverse Proxy Server) 환경에서 생기는 문제
Reverse Proxy Server 뒤에 애플리케이션 서버를 두는 경우에 다음과 같은 이슈가 생깁니다.
- 클라이언트가 실제로 HTTPS (혹은 HTTP) 로 접속했는지, 애플리케이션 서버가 알기 어습니다. Proxy 에서 SSL 을 해제하고 내부적으로 HTTP 로 넘길 수 있기 때문입니다.
- 클라이언트의 실제 IP 주소가 프록시 서버의 IP 로 보일 수 있습니다. 즉,
req.connection.remoteAddress등이 클라이언트가 아닌 프록시 IP 로 나올 수 있습니다.
- 호스트명(host), 포트(port), 스킴(scheme, HTTP vs HTTPS) 등이 프록시 환경에서 왜곡되거나 누락될 수 있습니다.
이런 문제들 때문에 애플리케이션 레벨에서
내 뒤에 신뢰할 수 있는 프록시가 있다 는 것을 인식하고, 프록시가 전달해준 헤더(e.g. X‑Forwarded‑Proto, X‑Forwarded‑For, X‑Forwarded‑Host) 를 신뢰하거나 해석할 수 있도록 설정해주는 것이 중요합니다.1.2 X‑Forwarded‑Proto Header
X‑Forwarded‑ProtoHeader 는 클라이언트가 어떤 스킴(scheme) 으로 요청했는지를 프록시가 애플리케이션 서버에 전달하는 용도로 흔히 사용됩니다.
- 예를 들어 클라이언트가
https://example.com으로 접속했고 프록시가 SSL 종료(termination) 를 한 뒤 내부적으로 애플리케이션 서버로http://요청을 한다면, 프록시는X‑Forwarded‑Proto: httpsHeader 를 붙여서원래는 HTTPS 였어요라고 알려줄 수 있습니다.
- 애플리케이션 서버에서는 이 헤더를 보고
req.protocol(Express 기준) 을https로 인식하게 할 수 있습니다.
- 다만 이 헤더가 클라이언트에서 임의로 조작될 수 있으므로,
신뢰할 수 있는 프록시 뒤에서만 헤더를 믿겠다 (trust proxy)식의 설정이 필요합니다.
- 또한
nginx등의 설정에서proxy_set_header X‑Forwarded‑Proto $scheme;같은 형태로 설정할 때 주의해야 합니다. 만약Load Balancer → nginx → Application라인의 흐름이라면 중간에서 헤더가 덮어쓰기(overwrite) 될 수 있습니다.
1.3 trust proxy 설정 (Express.js 기준)
- Express 앱에서는 기본적으로
app.set('trust proxy', false)상태이고, 프록시 뒤에 있는 경우true혹은 적절한 값으로 설정해야 프록시 헤더를 해석하게 됩니다.
trust proxy가 활성화되면 다음과 같은 변화가 일어납니다.req.ip,req.ips가X‑Forwarded‑ForHeader 를 참고합니다.req.protocol이X‑Forwarded‑ProtoHeader 를 참고하게 됩니다.req.hostname이X‑Forwarded‑HostHeader 를 참고하게 될 수 있습니다.
trust proxy설정 값은 단순true외에도 숫자, IP 주소 목록, 서브넷(Subnet) 등이 가능합니다. 예를 들어몇 번째 프록시까지 믿겠다는 숫자 형태가 가능합니다.
- 개인적인 생각으로는 실제 운영 환경에서는
내 앞에 있는 Proxy 혹은 Load Balancer IP 가 무엇인가?를 파악하고, 서브넷(Subnet) 범위를 지정해 주는 것이 더 안전하다고 생각합니다. 단지true로만 설정한 경우 헤더 스푸핑(Header Spoofing) 위험이 더 커질 수 있을 것 같습니다.
2. 왜 Proxy + Header + 설정이 중요한가
2.1 HTTPS 리디렉션, “secure cookie” 등 스킴 인식이 중요할 때
많은 웹 애플리케이션에서
HTTPS 로 접속했는가? 여부에 따라 동작이 달라지는데, 예시를 들어보면 다음 정도를 들어볼 수 있을 것 같습니다.- 세션 쿠키 설정 시
secure옵션이 붙으면 HTTPS 로 접속할 때만 쿠키가 전달됩니다.
- HTTPS 여부에 따라 Redirection (HTTP → HTTPS) 을 수행할 수 있습니다.
Proxy 뒤에서 애플리케이션 서버는 실제로는 내부 HTTP 로 통신할 수 있기 때문에,
클라이언트가 HTTPS로 접속했는가? 여부를 Application Level 에서 인식할 수 없으면 HTTPS 기반 기능이 깨질 수 있습니다.따라서
X‑Forwarded‑Proto Header 로 원래 스킴을 알려주고, trust proxy 로 Header 를 신뢰하도록 설정해야 합니다.2.2 클라이언트 실제 IP 파악이 필요할 때
보안로그, 접근 제어, rate limit, IP 기반 필터링 등의 경우 클라이언트의 실제 IP 가 중요합니다.
프록시 뒤면
req.connection.remoteAddress 는 Proxy IP 일 수밖에 없고, Application Server 만으로는 Client IP 를 알기 어렵습니다.이때
X‑Forwarded‑For Header 가 사용되고, trust proxy 설정이 되어야 req.ip 등이 실제 클라이언트 IP로 동작하게 됩니다.개인적인 생각으로는 Client IP 를 정확히 처리하고자 한다면, Proxy Chain 을 정확히 이해하고, 허용된 Proxy 에 대해서만 Header 를 신뢰하도록 설정하는 것이 필수입니다.
2.3 호스트명 / 포트 / 원본 요청 정보 보존
특히 프록시가 호스트 변경, 포트변경, URI Rewrite 등 작업을 수행할 때 애플리케이션 서버가
원래 요청 호스트명은 무엇이었나?, 원래 포트는? 등 정보를 알아야 할 경우가 있습니다.이런 상황에서도
X‑Forwarded‑Host, X‑Forwarded‑Port 등이 사용될 수 있습니다. 이 내용은 Express 공식 문서에서도 언급되어 있습니다. 3. 실제 구성 예시 및 Checklist 정리
3.1 프록시(nginx) 설정 예시
아래는 nginx 를 활용한 리버스 프록시 설정에서 TLS 종료 이후에 내부 Express 에 Request 을 전달하는 예시입니다.
위 설정에서 중요한 포인트는 다음과 같습니다.
X‑Forwarded‑Proto $scheme;- 클라이언트 →
http또는https같은 프록시 스킴을 내부로 전달
X‑Forwarded‑For $proxy_add_x_forwarded_for;- 기존
X‑Forwarded‑For가 있다면 덧붙이고, 없으면 클라이언트 IP 로 시작합니다. - 프록시 체인 전체를 보존하는 데 도움이 됩니다.
Host,X‑Forwarded‑Host,X‑Real-IP등 속성도 원본 요청 정보를 담기 위해 설정됩니다.
주의사항이 있다면 다음 정도를 생각해볼 수 있습니다.
- 만약 위 설정에서
$scheme이 항상http가 되도록 내부 통신이http로 이루어지는 경우라고 해도, 애플리케이션은https 요청이었음에도http로 인식할 수 있습니다.
- 실제로 Stack Overflow 같은 웹 커뮤니티에서 여러 이런 관련 문제들이 보고되기도 합니다.
“In nginx, you are overwriting the X‑Forwarded‑Proto header … value of $scheme when you send the request to Express. So … from Express’s point of view, the request is not secure.” Stack Overflow
따라서 쉽지 않고 생각한대로 잘 되지 않지만, 프록시 환경을 정확히 이해하고 header 덮어쓰기(overwrite) 여부와 흐름을 설계해야 한다고 생각합니다.
3.2 Express 측 설정 예시
Express 앱의 설정 예시들 들어보겠습니다.
설정 포인트
app.set('trust proxy', 1)
내 앞에 1 hop 의 프록시가 있고, 해당 프록시를 신뢰한다는 의미입니다.
app.set('trust proxy', true)
단순하게 모든 프록시 뒤를 신뢰하겠다는 의미인데, 다만 이 경우에는 누가 헤더를 조작할 수 있는가를 고려해야 합니다.
- 이후
req.protocol,req.ip,req.ips등이 올바르게 동작하는지 로그나 테스트로 확인해보는 것이 좋습니다.
3.3 설정 체크리스트
정리하자면 리버스 프록시 환경에서 이 두 가지를 확인해야 합니다.
- 프록시가 내부 애플리케이션 서버에 원본 요청의 스킴, IP, 호스트명 등 정보를 제대로 전달하고 있는가?
X‑Forwarded‑Proto,X‑Forwarded‑For,X‑Forwarded‑Host,X‑Forwarded‑Port등이 설정되어 있는가?
- Express 등 Application 이 Proxy 뒤에 있다는 것을 인식하고, 전달된 Header 를 신뢰하도록 설정되어 있는가?
trust proxy설정이 적절하게 되어 있는지 체크해보기- 헤더 스푸핑(Spoofing) 위험을 고려하여, IP / Subnet 같은 신뢰할 수 있는 프록시 범위를 설정했는지 체크해보기
- 실제 로그 및 테스트를 통해
req.protocol,req.ip,req.hostname등 정보들이 정상적으로 받아오고 있는지? - 예를 들어 HTTPS 로 접속했을 때
req.protocol === 'https'이 나오는가? - Client 실제 IP 주소가 Proxy IP 주소가 아니라
req.ip값과 일치하는가?
- 보안(Secure) 관련 설정이 깨지지 않는가?
- 쿠키
secure옵션이 제대로 작동하는가? - HTTP → HTTPS Redirection 로직이 Proxy 뒤에서 정상 동작하는가?
- X‑Forwarded Header 기반의 Access Control 혹은 IP Filtering 이 제대로 동작하는가?
4. 학습하면서 다시 정리해봤던 내용
4.1 기존 이해
- “
trust proxy = true이면, 무조건 프록시 뒤에서 헤더를 모두 믿는 것이다” 라고 단순하게 이해했다가, 실제로는 누가 Header 를 보낼 수 있는가, Proxy 인가 아닌가, Header 가 Overwrite 될 위험이 있는가 등을 고려해야 한다는 점을 놓쳤습니다.
X‑Forwarded‑Proto가 자동으로 설정되는 줄 알았는데, 실제로는 nginx 등 도구를 통해 구성된 프록시 설정에서proxy_set_header X‑Forwarded‑Proto …등 지시어를 별도로 설정해야 가능하다는 점을 학습했습니다.
Load Balancer -> Proxy -> Application처럼 Proxy Chain 이 여러 개인 경우일 때,몇 번의 홉(hop) 을 신뢰할 것인가에 대한 설정도 복잡해질 수 있다는 점을 학습할 수 있었습니다.
4.2 새로 학습한 내용
trust proxy설정은 헤더를 신뢰하겠다는 의미이지, 자동으로 헤더를 설정해 주는 것은 아니라고 합니다.- 따라서 프록시 설정과 애플리케이션 설정이 함께 맞춰져야 합니다.
X‑Forwarded‑Proto등의 헤더가 제대로 전달되지 않으면, 내부적으로 HTTP 로 전달되더라도 애플리케이션이 HTTPS 로 인식하지 못해 보안 기능이 깨질 수 있습니다.- e.g. 세션 쿠키
secure가 작동하지 않거나, Redirection 자체가 무의미해질 수 있습니다.
- Load Balancer 가 앞단에 있는 경우나, 여러 개의 Proxy 구성으로 되어 있는 경우라면,
신뢰할 프록시 수 / 범위설정이 매우 중요하다고 합니다. - e.g.
app.set('trust proxy', 2) - e.g.
app.set('trust proxy', ['10.0.0.0/8', '192.168.0.0/16'])
- Header Spoofing 위험이 존재하므로
단순히 Header 가 있으면 믿겠다보다는이 Header 는 신뢰할 수 있는 Proxy 가 붙여준 것이다라는 조건을 확보해야 한다고 합니다. - 예를 들어 애플리케이션 서버가 외부의 임의 HTTP Request 를 직접 받는 구조라면 Header 가 쉽게 조작될 수 있기 때문입니다.
5. 개인적으로 든 생각
- Production 환경에서는
trust proxy를 단순true보다 명시적으로 프록시 IP 나 Subnet 으로 설정하는 것이 더 안전하다는 생각이 들었습니다. - 이유는 Header Spoofing 위험과 앞으로 Proxy Structure 가 바뀔 가능성이 있기 때문입니다.
nginx설정에서proxy_set_header X‑Forwarded‑Proto $scheme;가 무조건 옳은 방식도 아니라는 생각이 들었습니다.- 왜냐하면 만약 Load Balancer 가 앞단에 있고 이미
X‑Forwarded‑ProtoHeader 를 붙였다면, nginx 가$scheme로 덮어써서 잘못된 스킴이 전달될 수 있기 때문입니다. - 실제로 그런 문제가 Stack Overflow 에 보고된 사례들이 있습니다.
- Header 가 여러 개 쌓이는
Load Balancer -> CDN -> nginx -> Application같은 Proxy Chain 구조에서는몇 홉을 믿어야 하는가 (trust proxy 값)가 애매해질 수 있어서, 이를 문서로 따로 명시해두는 것이 좋을 것 같습니다. - 제가 지금 구현하고 있는 코드에서 이를 적용할 수 있을 지 잘 모르겠어서, 솔선수범이 될 지 모르겠지만, 우선 학습하고 이해한 내용을 정리해서, 제 생각을 전달해봤습니다.
- Local 에서는 Proxy 설정 없이 직접 Express 서버로 접속한다든지 등 Development 환경과 Production 환경이 다를 경우
trust proxy값을 Environment Variable 로 관리하거나 Runtime 환경을 바탕으로 조건부로 설정하는 것도 좋을 것 같다는 생각을 했습니다.
6. 결론, 그리고 추가 학습 거리
- 결론적으로, Reverse Proxy 뒤에서 애플리케이션을 운영할 경우
X‑Forwarded‑Proto같은 헤더와trust proxy설정은 매우 중요합니다. 이를 통해 요청 스킴, 클라이언트 IP, 원본 호스트명 등을 애플리케이션 레벨에서 올바르게 인식할 수 있게 된다고 합니다. - 다만
설정만 하면 끝이 아니라, Proxy Structure, Header Flow, Spoofing 가능성 등 보안(Security) 문제 등을 함께 고려해야 한다고 합니다.
- 추가 학습 거리
- Proxy Chain 이 많은 환경에서
trust proxy값 설정을 어떻게 할 지, Best Practice 알아보기 X‑Forwarded‑Host,X‑Forwarded‑Port,Forwarded헤더와의 정확한 정의와 관계- Load Balancer, CDN, AWS ELB, GCP LoadBalancer 등 Cloud Proxy 환경에서의 실제 Header Flow
- 보안(Security) 측면에서 Header Spoofing 을 효과적으로 막는 방법
- 로깅(Logging), 모니터링(Monitoring) 도구 및 기법
앞으로 실무 경험 및 추가 학습을 통해서 본 포스트에서 일부 오해하거나 빠뜨린 부분과 추가할 부분이 발견된다면 계속해서 업데이트해갈 예정이니, 이를 감안하여 읽어주시기 바랍니다!
