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:
- Uses the standard
forwardfor
option as baseline - Manually sets X-Client-IP using the source IP
- 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:
option forwardfor
: Maintains standard X-Forwarded-For behaviorhttp-request set-header
: Forces X-Client-IP to contain the true client IPhttp-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.