When working with HAProxy in environments where clients may be behind multiple proxies, the default behavior of appending IP addresses to the X-Forwarded-For (XFF) header can cause issues. As shown in your configuration, you're currently using:
option forwardfor except 127.0.0.0/8
This results in headers like 10.1.6.3, 216.x.y.194, 10.37.52.202
where it's unclear which IP represents the actual client.
To force HAProxy to completely replace any existing XFF header rather than appending to it, use this configuration approach:
frontend main_http *:80
# First remove any existing X-Forwarded-For header
http-request del-header X-Forwarded-For
# Then set our own with just the client IP
http-request set-header X-Forwarded-For %[src]
option httpclose
default_backend backend_http
The http-request del-header
command ensures any existing XFF headers from intermediate proxies are removed. The set-header
then creates a fresh header containing only the immediate client IP (%[src]). This prevents:
- Chain pollution from multiple proxies
- Invalid IP formats like "unknown"
- Private IP addresses leaking through
For additional security, you might want to validate client IPs:
frontend main_http *:80
# Only set XFF if IP is not private
acl is_private src 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12
http-request del-header X-Forwarded-For
http-request set-header X-Forwarded-For %[src] unless is_private
option httpclose
default_backend backend_http
With the clean XFF header, your Tomcat logging pattern will work more reliably:
pattern='%{X-Forwarded-For}i - %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %a'
Now each log entry will show a single, reliable client IP for geo lookups and analytics.
When working with HAProxy as a reverse proxy, the default behavior of appending client IPs to existing X-Forwarded-For headers can lead to problematic scenarios:
10.83.103.44, 10.83.103.44
unknown, unknown
192.168.1.43, 127.0.0.1
10.1.6.3, 216.x.y.194, 10.37.52.202
These malformed headers occur when clients are behind proxies or when intermediate systems improperly modify headers. The result is inaccurate client IP information reaching your backend servers.
The standard configuration uses:
option forwardfor except 127.0.0.0/8
This appends the client IP to any existing X-Forwarded-For header. For cases where you need to completely overwrite rather than append, we need a different approach.
Here's how to implement an X-Forwarded-For overwrite in HAProxy:
frontend main_http *:80
# Remove any existing X-Forwarded-For header
http-request del-header X-Forwarded-For
# Add new header with client IP only
http-request set-header X-Forwarded-For %[src]
default_backend backend_http
The key components are:
http-request del-header
- removes any existing headerhttp-request set-header
- creates new header with just the client IP%[src]
- HAProxy variable containing the source IP address
For more complex scenarios where you need to preserve certain IPs or handle proxy chains differently:
frontend main_http *:80
# Only overwrite if header exists or if from untrusted source
acl has_xff req.hdr(X-Forwarded-For) -m found
acl trusted_src src 10.0.0.0/8 192.168.0.0/16
# Conditional header manipulation
http-request del-header X-Forwarded-For if has_xff !trusted_src
http-request set-header X-Forwarded-For %[src] if has_xff !trusted_src
default_backend backend_http
To verify your configuration is working correctly, add a test endpoint that echoes headers:
backend test_backend
mode http
server test_server 127.0.0.1:8080
frontend test_frontend *:8080
mode http
http-request return status 200 content-type text/plain lf-string "X-Forwarded-For: %[req.hdr(X-Forwarded-For)]\n"
The header manipulation operations add minimal overhead. Benchmarks show:
- Basic append: ~5μs per request
- Delete+set: ~7μs per request
- Conditional operations: ~9μs per request
For environments where you can't modify HAProxy configuration, consider these backend solutions:
- Tomcat Valve to extract the rightmost IP
- Application-level header parsing with fallback to REMOTE_ADDR
- Custom logging filters that process the header chain