Correcting nginx real_ip_header Behavior: Handling X-Forwarded-For Client IP Extraction


19 views

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:

  1. Using the http_realip_module with custom parsing
  2. Implementing Lua scripting for advanced IP extraction
  3. 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:

  1. Start checking from the rightmost IP
  2. Iterate leftward until finding an untrusted IP
  3. 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