로그에 찍힌 IP가 사용자 주소처럼 보이죠? 사실은 바로 앞 프록시 주소일 수도 있어요.Proxy, Reverse Proxy, 그리고 Load Balancer에서는 브라우저와 앱 서버 사이에 리버스 프록시나 로드 밸런서가 설 수 있다는 큰 그림을 봤어요. 그리고 502, 503, 504는 어디서 만든 응답일까요?에서는 앞단과 오리진 사이에서 응답의 목소리가 달라질 수 있다는 걸 봤죠. 이번에는 응답 코드가 아니라 클라이언트 IP를 읽어볼게요. 운영 로그에 이런 줄이 있다고 해볼게요.
203.0.113.25가 사용자 같고, 10.0.4.12는 내부 주소처럼 보여요. 맞을 수도 있어요. 하지만 그냥 맨 앞 값을 믿으면 안 돼요.
오늘의 질문은 이거예요.
“이 IP는 누가 적은 값이고, 어디까지 믿어도 될까요?”
Forwarded 헤더는 RFC 7239에서 표준화한 HTTP 확장 헤더예요. 이 RFC는 프록시가 끼면 원래 클라이언트 IP, 원래 Host, 원래 protocol 같은 정보가 사라지거나 바뀔 수 있고, 이를 HTTP 헤더로 전달할 수 있다고 설명해요. 동시에 X-Forwarded-For, X-Forwarded-By, X-Forwarded-Proto 같은 헤더는 흔히 쓰이는 비표준 계열이라고 짚어요.
여기서는 특정 프레임워크 설정법보다,
X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host, 표준 Forwarded 헤더를 어떤 순서로 읽고 어디까지 신뢰해야 하는지에 집중해요. 제품마다 헤더를 붙이는 방식과 trust proxy 설정 이름은 달라요.앱 서버가 처음 보는 주소는 “바로 앞 사람”이에요
택배를 생각해볼게요. 고객이 택배를 보냈는데, 중간 물류센터가 여러 번 갈아 실었어요. 마지막 창고 직원이 상자를 받으면, 상자를 손에 건넨 사람은 고객이 아니라 바로 앞 물류센터 직원이에요. 웹 서버도 비슷해요. 앱 서버 입장에서 TCP 연결을 직접 열고 들어온 상대는10.0.4.12일 수 있어요. 그래서 앱의 remote address나 remote_addr에는 사용자가 아니라 로드 밸런서 주소가 찍힐 수 있죠.
핵심은 이거예요.
앱이 직접 본 주소와, 헤더에 적힌 원래 주소는 성격이 달라요.
직접 본 주소는 네트워크 연결에서 나온 값이고, forwarded 계열 헤더는 HTTP 요청 안에 적힌 값이에요. HTTP 헤더는 중간 프록시가 붙여줄 수도 있지만, 클라이언트가 마음대로 보내버릴 수도 있어요.
X-Forwarded-For 순서는 신뢰하는 프록시 체인 안에서만 의미가 있어요
가장 자주 보는 건X-Forwarded-For예요.
remote_addr가 10.0.4.12이고, 헤더가 203.0.113.25, 198.51.100.7이라면 전체 후보 체인은 이렇게 볼 수 있어요.
X-Forwarded-For는 표준 Forwarded보다 오래 널리 쓰인 관례에 가깝고, 무엇보다 요청 헤더라서 위조될 수 있기 때문이에요.
먼저 신뢰할 프록시를 정해야 해요
클라이언트 IP를 안전하게 읽으려면 이런 질문을 해야 해요.
“우리 앱 바로 앞의 remote_addr는 신뢰하는 프록시인가요?”
이 흐름이 중요한 이유가 있어요. 공격자가 맨 왼쪽에 가짜 IP를 넣어도, 우리가 오른쪽에서부터 신뢰하는 프록시를 벗겨내는 방식으로 읽으면 위험을 줄일 수 있어요.
예를 들어 이런 값이 있다고 해볼게요.
그래서 이 예시에서는 맨 왼쪽
10.1.2.3이 아니라 203.0.113.25를 사용자 후보로 읽는 쪽이 더 안전해요.
X-Forwarded-Proto와 Host는 URL 판단에 영향을 줘요
forwarded 계열 헤더는 IP만 다루지 않아요. 프록시가 TLS를 앞단에서 끝내면, 앱 서버와 프록시 사이는 내부 HTTP일 수 있어요. 앱이 자기 요청을 내부 HTTP로만 보면 이런 문제가 생길 수 있어요.
하지만
Host와 Proto도 마찬가지로 신뢰 문제가 있어요. 외부 사용자가 보낸 X-Forwarded-Host를 그대로 믿고 redirect URL을 만들면, open redirect나 잘못된 링크 생성으로 이어질 수 있어요.
그래서 일반 원칙은 이렇게 잡는 게 좋아요.
- 앱 바로 앞 프록시가 신뢰되는지 확인해요.
- 프록시가 외부에서 온 forwarded 계열 헤더를 정리하는지 확인해요.
- 앱은 신뢰 프록시가 보장한 헤더만 사용해요.
- 보안 판단에는 allowlist와 별도 정책을 같이 둬요.
표준 Forwarded 헤더는 한 줄에 묶어서 표현해요
X-Forwarded-*가 널리 쓰이지만, 표준화된 헤더는 Forwarded예요.
Forwarded가 프록시 과정에서 사라지거나 바뀐 정보를 전달하기 위한 선택적 요청 헤더라고 설명해요. 주요 parameter는 아래처럼 읽을 수 있어요.
Forwarded의 장점은 for, proto, host 같은 값이 한 요소 안에 묶여 있어 어떤 프록시가 어떤 값을 붙였는지를 더 잘 맞춰볼 수 있다는 점이에요. 반대로 X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host는 서로 다른 헤더라서 여러 프록시가 섞일 때 값의 짝을 맞추기 어려울 수 있어요.
다만 현실에서는 여전히 X-Forwarded-*를 더 많이 만날 수 있어요. 그래서 둘 중 하나만 “정답”으로 외우기보다, 내가 쓰는 CDN, 로드 밸런서, 프레임워크가 무엇을 붙이고 무엇을 읽는지 확인해야 해요.
로그에서는 네 가지를 같이 남겨요
클라이언트 IP 문제를 나중에 디버깅하려면 한 값만 남기면 부족해요.
예를 들면 이런 식이에요.
어디서 자주 문제가 생길까요?
모든 사용자가 같은 IP로 보일 때
앱이X-Forwarded-For를 전혀 읽지 않고 remote_addr만 보면 모든 사용자가 로드 밸런서 IP로 보일 수 있어요. 그러면 rate limit, abuse 탐지, 지역 통계가 전부 이상해져요.
아무나 내부 IP인 척할 수 있을 때
반대로 앱이X-Forwarded-For를 무조건 믿으면 사용자가 127.0.0.1이나 사내 IP를 헤더에 넣어서 내부 사용자처럼 보이려 할 수 있어요. IP 기반 접근 제어가 있다면 특히 위험해요.
HTTPS인데 앱은 HTTP라고 착각할 때
TLS가 프록시에서 끝나고 앱에는 HTTP로 들어오면, 앱은 외부 요청이 HTTPS였는지 모를 수 있어요. 이때X-Forwarded-Proto를 안전하게 읽지 못하면 http:// redirect가 생기거나 secure cookie 설정이 흔들릴 수 있어요.
Host 기반 라우팅이 꼬일 때
멀티테넌트 서비스나 여러 도메인을 한 앱에서 받는 구조에서는 원래 Host가 중요해요. 프록시가 내부 Host로 바꿔서 보내는데 앱이X-Forwarded-Host를 안전하게 처리하지 못하면 tenant 선택, canonical URL, OAuth callback URL이 꼬일 수 있어요.
잘못 읽기 쉬운 함정
X-Forwarded-For의 맨 왼쪽을 항상 진짜 사용자로 보기
맨 왼쪽 값은 진짜일 수도 있지만, 사용자가 직접 넣은 가짜 값일 수도 있어요. 신뢰 프록시 목록을 기준으로 오른쪽에서부터 확인해야 해요.
remote_addr를 항상 진짜 사용자로 보기
프록시 뒤의 앱에서 remote_addr는 보통 바로 앞 프록시 주소예요. 이 값만 보면 모든 사용자가 같은 주소처럼 보일 수 있어요.
프록시가 헤더를 정리해줄 거라고 가정하기
앞단이 외부에서 들어온X-Forwarded-*를 지우고 새로 붙이는지, 기존 값을 append하는지, 아예 건드리지 않는지는 설정에 따라 달라요. 확인 없이 앱에서 믿으면 안 돼요.
X-Forwarded-Proto만 보면 보안 판단이 끝난다고 보기
proto=https도 헤더 값이에요. 신뢰 프록시가 보장한 값인지 확인해야 하고, 중요한 보안 판단은 별도 allowlist, HSTS, secure cookie 정책과 함께 봐야 해요.
표준 Forwarded만 쓰면 모든 문제가 사라진다고 보기
Forwarded는 더 구조화된 표준 헤더지만, 그래도 요청 헤더예요. 신뢰할 수 없는 경로에서 들어온 값은 여전히 믿으면 안 돼요.
자, 정리해볼까요?
- 프록시 뒤의 앱 서버가 직접 보는
remote_addr는 실제 사용자 IP가 아니라 바로 앞 프록시 주소일 수 있어요. X-Forwarded-For는 원래 클라이언트와 중간 프록시 체인을 남기는 데 널리 쓰이지만, 비표준 계열이고 위조될 수 있어요.- 안전하게 읽으려면
remote_addr가 신뢰 프록시인지 확인하고, 오른쪽에서 왼쪽으로 신뢰 프록시를 벗겨가며 클라이언트 후보를 찾아야 해요. X-Forwarded-Proto,X-Forwarded-Host는 redirect, secure cookie, absolute URL, tenant 선택에 영향을 줄 수 있어요.- 표준
Forwarded헤더는for,by,host,proto를 한 줄에 묶어 표현하지만, 이것도 신뢰 경계 없이 믿으면 안 돼요. - 로그에는 raw 헤더와 계산된
client_ip를 함께 남겨야 나중에 오해를 줄일 수 있어요.
이어서 보면 좋은 글
- Proxy, Reverse Proxy, 그리고 Load Balancer - 왜 앱 앞에 중간자가 서는지 큰 그림을 다시 볼 수 있어요.
- End-to-End Request Debugging - 요청 하나가 어떤 체크포인트를 지나 앱까지 오는지 이어서 볼 수 있어요.
- 502, 503, 504는 어디서 만든 응답일까요? - 응답 코드도 어느 계층이 만들었는지 같이 읽어볼 수 있어요.
- HTTP/1.1 메시지는 왜 빈 줄 하나가 중요할까요? - forwarded 값이 결국 HTTP 헤더 안에 들어간다는 구조를 더 자세히 볼 수 있어요.