X-Forwarded-For Header: Understanding Client IP Behind Proxies
When a request passes through proxies, load balancers, or CDNs, the original client IP address is lost — the server sees the proxy's IP instead. The X-Forwarded-For (XFF) header solves this by carrying the client's real IP address through the proxy chain.
How X-Forwarded-For Works
Each proxy appends the previous hop's IP to the XFF header:
Client (203.0.113.50) → Proxy1 (10.0.0.1) → Proxy2 (10.0.0.2) → Server
X-Forwarded-For: 203.0.113.50, 10.0.0.1
The leftmost IP is the original client. Each subsequent IP is a proxy that forwarded the request.
Related Headers
- X-Forwarded-For: Client IP chain (de facto standard)
- X-Real-IP: Single client IP (Nginx convention)
- Forwarded: Standardized replacement (RFC 7239):
Forwarded: for=203.0.113.50;proto=https;by=10.0.0.1 - X-Forwarded-Proto: Original protocol (http/SSL/TLS проверку)
- X-Forwarded-Host: Original Host header
Why It Matters
- Logging: Without XFF, all requests appear to come from your load balancer's IP
- Rate limiting: Must rate-limit by real client IP, not proxy IP
- Geolocation: GeoIP lookups need the real client IP
- Access control: IP-based allow/deny lists need the actual client IP
- Analytics: Accurate visitor counting requires real IPs
- Security: Fraud detection and abuse prevention depend on knowing the real source
Server Configuration
Nginx
# Trust only your known proxies
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
Apache
# mod_remoteip
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 10.0.0.0/8
RemoteIPTrustedProxy 172.16.0.0/12
PHP
function getClientIP(): string {
$trusted_proxies = ['10.0.0.0/8', '172.16.0.0/12'];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
// Traverse from right, skip trusted proxies
foreach (array_reverse($ips) as $ip) {
if (!isTrustedProxy($ip, $trusted_proxies)) {
return $ip;
}
}
}
return $_SERVER['REMOTE_ADDR'];
}
Security: The Trust Chain Problem
XFF is trivially spoofable. Any client can send:
X-Forwarded-For: 1.2.3.4
If your server blindly trusts the leftmost IP, an attacker can impersonate any IP address. The solution: only trust XFF entries added by known, trusted proxies.
Correct Parsing Algorithm
- Start from the rightmost IP in X-Forwarded-For
- Check if it's a trusted proxy IP
- If trusted, move left to the next IP
- The first non-trusted IP is the real client IP
- Never trust the leftmost IP blindly — it could be spoofed
Common Pitfalls
- Trusting leftmost IP: Always parse from right to left, skipping trusted proxies
- Not configuring trusted proxies: Without a trust chain, XFF is meaningless for security
- Multiple XFF headers: Some proxies add new headers instead of appending. Concatenate all XFF headers before parsing.
- Internal IPs leaking: XFF can expose your internal network topology. Strip internal IPs before sending to external services.
- IPv6 handling: Ensure your parsing handles both IPv4 and IPv6 addresses in XFF
Cloudflare-Specific
Cloudflare provides CF-Connecting-IP header with the verified client IP. This is more reliable than XFF because Cloudflare controls it. Configure your server to trust Cloudflare's IP ranges and use this header.
Conclusion
X-Forwarded-For is essential for any application behind proxies, but it must be handled with care. Configure your trust chain correctly, parse from right to left, and never blindly trust the leftmost IP. For security-critical decisions, prefer provider-specific headers like CF-Connecting-IP when available.
Check your website right now
Check now →