How to Set Both X-Client-IP and X-Forwarded-For Headers with Client IP in HAProxy


2 views

When configuring HAProxy as a reverse proxy, many developers need to pass the client's original IP address through both X-Client-IP and X-Forwarded-For headers simultaneously. The default behavior using option forwardfor only populates X-Forwarded-For, while using option forwardfor header X-Client-IP only sets X-Client-IP.

The core issue lies in how HAProxy processes these directives:

# This only sets X-Forwarded-For
option forwardfor
option http-server-close

# This only sets X-Client-IP
option forwardfor header X-Client-IP
option http-server-close

HAProxy doesn't natively support setting both headers through simple configuration options.

To achieve dual-header functionality, we need to manually set the headers using http-request directives:

frontend http-in
    bind *:80
    option http-server-close
    option forwardfor
    
    # Manually set X-Client-IP
    http-request set-header X-Client-IP %[src]
    
    # Ensure X-Forwarded-For is properly set
    http-request set-header X-Forwarded-For %[src] if { hdr(X-Forwarded-For) -m found }
    default_backend servers

This configuration:

  1. Uses the standard forwardfor option as baseline
  2. Manually sets X-Client-IP using the source IP
  3. Ensures X-Forwarded-For maintains proper chaining when multiple proxies are involved

For more complex environments with multiple proxy layers:

http-request set-header X-Client-IP %[src]
http-request set-header X-Forwarded-For %[src],\ %[hdr(X-Forwarded-For)] if { hdr(X-Forwarded-For) -m found }
http-request set-header X-Forwarded-For %[src] if !{ hdr(X-Forwarded-For) -m found }

This properly appends IPs to X-Forwarded-For while maintaining X-Client-IP as the original client address.

After implementing, verify with:

curl -v http://yourhaproxy:port

Or check your IIS logs to confirm both headers contain the correct client IP address.


When working with HAProxy as a reverse proxy, properly passing client IP addresses becomes crucial for logging, security, and application logic. The standard approach using option forwardfor only handles X-Forwarded-For, while many enterprise applications expect both X-Client-IP and X-Forwarded-For headers.

The issue you're encountering stems from how HAProxy processes headers:

option forwardfor                  # Only sets X-Forwarded-For
option forwardfor header X-Client-IP # Only sets X-Client-IP

Neither approach simultaneously populates both headers with the client IP.

Here's the working configuration that maintains both headers:

frontend http-in
    bind *:80
    option forwardfor
    http-request set-header X-Client-IP %[src]
    http-request add-header X-Forwarded-For %[src] if { hdr_cnt(X-Forwarded-For) eq 0 }
    default_backend servers

This solution combines three key directives:

  1. option forwardfor: Maintains standard X-Forwarded-For behavior
  2. http-request set-header: Forces X-Client-IP to contain the true client IP
  3. http-request add-header: Ensures X-Forwarded-For exists when empty

For environments with multiple proxies, this enhanced version preserves the chain:

frontend http-in
    bind *:80
    option forwardfor
    http-request set-header X-Client-IP %[src]
    http-request add-header X-Forwarded-For %[src] if !{ hdr(X-Forwarded-For) }
    http-request set-header X-Forwarded-For %[src],\ %[hdr(X-Forwarded-For)] if { hdr(X-Forwarded-For) }
    default_backend servers

After applying the configuration, verify with:

curl -v http://yourhaproxy:port

Sample output should show:

> GET / HTTP/1.1
> Host: yourhaproxy:port
> 
< HTTP/1.1 200 OK
< X-Client-IP: 192.168.1.100
< X-Forwarded-For: 192.168.1.100

The additional header processing adds minimal overhead (typically <1% CPU impact) while providing valuable client information to backend servers.