현대 웹 아키텍처에서 X-Forwarded-For와 Forwarded 헤더가 중요한 이유 이해하기
Source: Dev.to
Introduction
웹 초창기에는 각 요청이 클라이언트에서 서버로 직접 전달돼 서버가 TCP 연결을 통해 누가 호출했는지 알 수 있었습니다. 현대 아키텍처에서는 CDN, 로드 밸런서, API 게이트웨이, 리버스 프록시 등 여러 중간 단계가 삽입되면서 원본 클라이언트 IP가 사라집니다. 요청이 애플리케이션에 도달했을 때 보통 마지막 프록시의 IP만 보이게 되죠. 이로 인해 로깅, 보안, 속도 제한, 지리 위치 파악, 문제 해결 등이 훨씬 어려워집니다.
X-Forwarded-For와 Forwarded 헤더는 이러한 홉을 거쳐도 클라이언트의 정체성을 보존하기 위해 만들어졌습니다.
How X-Forwarded-For Works
X-Forwarded-For(XFF)는 원본 IP를 노출하기 위한 사실상의 표준입니다. 각 프록시는 자신이 요청을 받은 IP 주소를 헤더에 추가하며, 콤마로 구분된 리스트가 생성됩니다:
X-Forwarded-For: 203.0.113.50, 198.41.215.10
- 가장 왼쪽 값이 원본 클라이언트입니다.
- 가장 오른쪽 값이 가장 최근에 거친 프록시입니다.
프록시가 여러 개일 경우 헤더가 단계별로 늘어나면서 애플리케이션이 전체 경로를 재구성할 수 있게 됩니다.
The Standard Forwarded Header (RFC 7239)
Forwarded 헤더는 동일한 개념을 키‑값 구문으로 정형화한 것입니다:
Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
지원되는 파라미터:
| Parameter | Meaning |
|---|---|
for | 클라이언트 주소 |
proto | 원본 스킴(http 또는 https) |
by | 프록시 식별자 |
host | 원본 Host 헤더 |
여러 프록시가 있을 경우 엔트리를 콤마로 구분해 모호성을 줄이고 선택적 메타데이터를 포함할 수 있습니다.
Security Considerations
클라이언트가 어떤 헤더든 위조할 수 있기 때문에, 포워드된 값을 무조건 신뢰하는 것은 위험합니다. 더 안전한 전략:
- 신뢰할 수 있는 프록시 IP 대역에서 온 경우에만 포워드된 헤더를 신뢰합니다.
- 각 홉을 검증하고, 첫 번째 신뢰되지 않는 주소를 실제 클라이언트로 간주합니다.
Trusted Parsing Example (Python)
import ipaddress
TRUSTED_PROXIES = {
ipaddress.ip_network('10.0.0.0/8'),
ipaddress.ip_network('198.41.128.0/17')
}
def get_trusted_client_ip(xff_header: str | None, connection_ip: str) -> str:
"""
Return the first untrusted IP from the X-Forwarded-For chain.
If the header is missing, fall back to the direct connection IP.
"""
if not xff_header:
return connection_ip
# Build the full chain: XFF entries + the immediate connection IP
ips = [ip.strip() for ip in xff_header.split(',')]
ips.append(connection_ip)
# Walk the chain from the client outward, stopping at the first
# address that is *not* in a trusted network.
for ip_str in reversed(ips[:-1]): # skip the last (direct) IP
try:
ip = ipaddress.ip_address(ip_str)
if not any(ip in net for net in TRUSTED_PROXIES):
return ip_str
except ValueError:
continue
# All hops are trusted → the leftmost entry is the client
return ips[0]
Configuring Popular Web Servers
NGINX
# Trust internal proxy ranges
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# Example rate‑limit zone using the resolved client IP
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
Apache
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 10.0.0.0/8
AWS Application Load Balancer (ALB)
ALB는 X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host를 자동으로 삽입합니다. 애플리케이션에서는 이 값들을 올바르게 파싱하기만 하면 됩니다.
Cloudflare
Cloudflare는 검증된 클라이언트 주소를 CF-Connecting-IP 헤더에 추가합니다:
CF-Connecting-IP: 203.0.113.50
Using Forwarded Headers in Application Code
Flask (Python)
from flask import request
def get_client_ip():
xff = request.headers.get('X-Forwarded-For')
if xff:
# The first entry is the original client
return xff.split(',')[0].strip()
return request.remote_addr
Express (Node.js)
function getClientIP(req) {
const forwarded = req.headers['forwarded'];
if (forwarded) {
const match = forwarded.match(/for=["']?([^"',;\s]+)/i);
if (match) return match[1].replace(/^\[|\]$/g, '');
}
const xff = req.headers['x-forwarded-for'];
if (xff) return xff.split(',')[0].trim();
return req.socket.remoteAddress;
}
Additional Forwarded Headers
| Header | Purpose |
|---|---|
X-Forwarded-Proto | 원본 스킴(http/https) |
X-Forwarded-Host | 원본 Host 헤더 |
X-Real-IP | 단순화된 클라이언트 IP(주로 NGINX에서 사용) |
Via | 프록시와 프로토콜 체인을 표시 |
이 헤더들은 X-Forwarded-For/Forwarded와 함께 요청 경로에 대한 보다 완전한 정보를 제공합니다.
Conclusion
포워드된 헤더는 프록시가 다수 존재하는 현대 웹 아키텍처에 필수적입니다. 실제 클라이언트 정체성을 복원함으로써 정확한 로깅, 보안 분석, 속도 제한, 지리 위치 파악 및 접근 제어가 가능해집니다. 다만, 반드시 신뢰할 수 있는 프록시에서만 헤더를 받아야 하며 추출한 값을 항상 검증해야 합니다. 올바르게 사용한다면 X-Forwarded-For와 표준화된 Forwarded 헤더는 사라질 수 있었던 컨텍스트를 보존하는 강력한 도구가 됩니다.