Solving Real Client IP Visibility in HAProxy: X-Forwarded-For Implementation and Best Practices


2 views

When implementing HAProxy as a load balancer, one common frustration emerges: all backend servers start seeing requests as originating from the HAProxy IP rather than the actual client IP. This breaks several critical functions:

# Problematic Apache logs showing HAProxy IP instead of client IP
192.168.1.100 - - [15/Jan/2023:10:22:45 +0000] "GET /secure/ HTTP/1.1" 403 298

The solution requires two key modifications to your HAProxy configuration:

frontend http-in
    bind *:80
    option forwardfor header X-Forwarded-For
    option forwardfor except 127.0.0.1
    http-request set-header X-Forwarded-For %[src]
    default_backend servers

backend servers
    server app1 10.0.0.1:80 check
    server app2 10.0.0.2:80 check

For Apache servers, enable the remoteip module:

# Apache configuration
LoadModule remoteip_module modules/mod_remoteip.so

RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 192.168.1.100  # HAProxy IP
RemoteIPInternalProxy 10.0.0.0/24    # Private network

For Nginx:

# Nginx configuration
real_ip_header X-Forwarded-For;
set_real_ip_from 192.168.1.100;
set_real_ip_from 10.0.0.0/24;

Test with curl to confirm IP forwarding works:

curl -H "X-Forwarded-For: 203.0.113.45" http://yourdomain.com/ip-test

For security-sensitive environments, consider these additional measures:

# Restrict IP spoofing
acl valid_ip hdr_ip(X-Forwarded-For) -m reg -i ^([0-9]{1,3}\.){3}[0-9]{1,3}$
http-request deny if !valid_ip

# Rate limiting by real IP
stick-table type ip size 1m expire 1h store http_req_rate(10s)
tcp-request content track-sc0 src

When implementing HAProxy as a reverse proxy for web applications, one common challenge is maintaining the original client IP addresses. By default, backend servers only see HAProxy's IP address, which breaks IP-based functionality in applications and security configurations.

The crucial configuration directive is option forwardfor, which we see in your current setup. However, we need to enhance it with proper headers:

option forwardfor header X-Forwarded-For
option forwardfor except 127.0.0.1

Here's an optimized version of your configuration with IP preservation:

global
      maxconn 4096
      pidfile /var/run/haproxy.pid
      daemon

defaults
      mode http
      retries 3
      option redispatch
      maxconn 2000
      contimeout 5000
      clitimeout 50000
      srvtimeout 50000

frontend http-in
      bind xxx.xxx.xxx.xxx:80
      option forwardfor header X-Forwarded-For
      http-request set-header X-Real-IP %[src]
      default_backend servers

backend servers
      balance roundrobin
      cookie GALAXY insert
      option httpclose
      stats enable
      stats auth username:userpass
      server app1 xxx.xxx.xxx.xxx:80 maxconn 1 check

For Apache, you'll need to configure mod_remoteip:

LoadModule remoteip_module modules/mod_remoteip.so

RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 127.0.0.1
RemoteIPInternalProxy xxx.xxx.xxx.xxx # HAProxy IP

Create a test PHP page with:

<?php
echo "Remote IP: " . $_SERVER['REMOTE_ADDR'] . "<br>";
echo "X-Forwarded-For: " . $_SERVER['HTTP_X_FORWARDED_FOR'] . "<br>";
echo "X-Real-IP: " . $_SERVER['HTTP_X_REAL_IP'] . "<br>";
?>

Always validate IP headers to prevent spoofing. In HAProxy, you can add:

acl valid_ip src -f /etc/haproxy/whitelist.lst
http-request deny if !valid_ip

For more robust IP preservation, consider PROXY protocol in HAProxy 1.5+:

server app1 xxx.xxx.xxx.xxx:80 send-proxy maxconn 1 check

Then configure your backend server to accept PROXY protocol connections.