According to RFC 7239 and common web infrastructure practice, the X-Forwarded-For
header follows this format:
X-Forwarded-For: client_ip, proxy1_ip, proxy2_ip
The left-most IP should represent the original client, with each subsequent proxy adding its own IP to the right.
nginx's real_ip_header
module surprisingly uses the right-most IP by default when processing X-Forwarded-For headers. This directly contradicts standard practice and creates security concerns:
# Problematic default configuration:
real_ip_header X-Forwarded-For;
set_real_ip_from 192.168.1.0/24;
Using the wrong IP can:
- Break rate limiting
- Compromise IP-based authentication
- Skew analytics data
- Prevent proper abuse detection
Modern nginx versions (1.11.0+) provide a solution through the real_ip_recursive
directive:
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 192.168.0.0/16;
Here's a production-ready configuration that properly handles multiple proxy hops:
http {
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# List all trusted proxies
set_real_ip_from 10.1.0.0/16;
set_real_ip_from 203.0.113.45;
set_real_ip_from 2001:db8::/32;
# Additional security
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
Test your configuration with this simple endpoint:
location /test-ip {
return 200 "Client IP: $remote_addr\n";
}
Send requests with different X-Forwarded-For headers to verify the behavior matches your expectations.
For complex environments, consider:
- Using the
http_realip_module
with custom parsing - Implementing Lua scripting for advanced IP extraction
- Moving IP resolution logic to the application layer
- Always enable
real_ip_recursive
when using X-Forwarded-For - Explicitly list all trusted proxy IPs in
set_real_ip_from
- Test your configuration with various proxy chain scenarios
- Consider security implications when exposing internal proxy IPs
When working with proxy chains in web architectures, the X-Forwarded-For
header follows a well-established convention where the client's original IP appears leftmost in the comma-separated list:
X-Forwarded-For: client_ip, proxy1_ip, proxy2_ip
However, nginx's real_ip_header
directive exhibits counterintuitive behavior by defaulting to the rightmost IP in this chain when processing X-Forwarded-For
headers.
This becomes problematic when:
- Your load balancer adds its IP to XFF headers
- Security modules need accurate client IPs for rate limiting
- Audit logs require true origin IP addresses
To make nginx correctly identify the leftmost IP as the client address, use this configuration:
set_real_ip_from 10.0.0.0/8; # Trusted proxy IP range
set_real_ip_from 192.168.1.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on; # Critical for proper parsing
When enabled, nginx will:
- Start checking from the rightmost IP
- Iterate leftward until finding an untrusted IP
- Use that as the client IP
Example with X-Forwarded-For: 203.0.113.45, 10.1.2.3, 192.168.1.100
:
- With
real_ip_recursive off
: Uses 192.168.1.100 - With
real_ip_recursive on
: Uses 203.0.113.45 (assuming 10.1.2.3 and 192.168.1.100 are in trusted ranges)
For simpler architectures where your first proxy can set a dedicated header:
proxy_set_header X-Real-IP $remote_addr;
Then configure nginx:
real_ip_header X-Real-IP;
Create a test endpoint to verify IP detection:
location /test-ip {
return 200 "Client IP: $remote_addr\n";
}
Test with curl:
curl -H "X-Forwarded-For: 1.2.3.4, 10.0.0.1" http://your-nginx/test-ip
Always:
- Restrict
set_real_ip_from
to known proxy IPs - Never trust XFF headers from untrusted networks
- Consider using the PROXY protocol for TCP services