현대 웹 아키텍처에서 X-Forwarded-For와 Forwarded 헤더가 중요한 이유 이해하기

발행: (2025년 12월 11일 오후 02:28 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

Introduction

웹 초창기에는 각 요청이 클라이언트에서 서버로 직접 전달돼 서버가 TCP 연결을 통해 누가 호출했는지 알 수 있었습니다. 현대 아키텍처에서는 CDN, 로드 밸런서, API 게이트웨이, 리버스 프록시 등 여러 중간 단계가 삽입되면서 원본 클라이언트 IP가 사라집니다. 요청이 애플리케이션에 도달했을 때 보통 마지막 프록시의 IP만 보이게 되죠. 이로 인해 로깅, 보안, 속도 제한, 지리 위치 파악, 문제 해결 등이 훨씬 어려워집니다.

X-Forwarded-ForForwarded 헤더는 이러한 홉을 거쳐도 클라이언트의 정체성을 보존하기 위해 만들어졌습니다.

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

지원되는 파라미터:

ParameterMeaning
for클라이언트 주소
proto원본 스킴(http 또는 https)
by프록시 식별자
host원본 Host 헤더

여러 프록시가 있을 경우 엔트리를 콤마로 구분해 모호성을 줄이고 선택적 메타데이터를 포함할 수 있습니다.

Security Considerations

클라이언트가 어떤 헤더든 위조할 수 있기 때문에, 포워드된 값을 무조건 신뢰하는 것은 위험합니다. 더 안전한 전략:

  1. 신뢰할 수 있는 프록시 IP 대역에서 온 경우에만 포워드된 헤더를 신뢰합니다.
  2. 각 홉을 검증하고, 첫 번째 신뢰되지 않는 주소를 실제 클라이언트로 간주합니다.

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]

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

HeaderPurpose
X-Forwarded-Proto원본 스킴(http/https)
X-Forwarded-Host원본 Host 헤더
X-Real-IP단순화된 클라이언트 IP(주로 NGINX에서 사용)
Via프록시와 프로토콜 체인을 표시

이 헤더들은 X-Forwarded-For/Forwarded와 함께 요청 경로에 대한 보다 완전한 정보를 제공합니다.

Conclusion

포워드된 헤더는 프록시가 다수 존재하는 현대 웹 아키텍처에 필수적입니다. 실제 클라이언트 정체성을 복원함으로써 정확한 로깅, 보안 분석, 속도 제한, 지리 위치 파악 및 접근 제어가 가능해집니다. 다만, 반드시 신뢰할 수 있는 프록시에서만 헤더를 받아야 하며 추출한 값을 항상 검증해야 합니다. 올바르게 사용한다면 X-Forwarded-For와 표준화된 Forwarded 헤더는 사라질 수 있었던 컨텍스트를 보존하는 강력한 도구가 됩니다.

Back to Blog

관련 글

더 보기 »