How to Implement HAProxy SSL Termination with Backend Re-encryption: X-Forwarded-For, Cookie Stickiness & URL Rewriting


1 views

Many modern architectures require SSL termination at the load balancer while maintaining encrypted connections to backend servers. Here's how to configure HAProxy for this dual-SSL scenario while handling advanced routing requirements.

global
    ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
    tune.ssl.default-dh-param 2048

defaults
    mode http
    timeout connect 10s
    timeout client 30s
    timeout server 30s

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    option forwardfor
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https
    
    # Cookie-based stickiness
    stick-table type string len 32 size 30k expire 30m
    stick store-request req.cook(JSESSIONID)
    stick match resp.cook(JSESSIONID)
    
    # URL rewriting example
    reqrep ^([^\ ]*)\ /oldpath/(.*) \1\ /newpath/\2
    
    # SSL termination with re-encryption
    use_backend secure_servers if { hdr(host) -i app.example.com }

backend secure_servers
    server s1 192.168.1.10:443 ssl ca-file /etc/haproxy/certs/backend-ca.pem verify required
    server s2 192.168.1.11:443 ssl ca-file /etc/haproxy/certs/backend-ca.pem verify required
    http-request set-header X-Forwarded-For %[src]

The configuration achieves several critical objectives:

  • Dual SSL Handling: Terminates client SSL at HAProxy then re-encrypts using ssl parameter in backend server definitions
  • Header Preservation: X-Forwarded-For maintains original client IP through the encrypted backend hop
  • Session Stickiness: Cookie-based persistence using stick-table tracks sessions via JSESSIONID

For backend verification, you'll need:

openssl s_client -connect backend:443 -showcerts < /dev/null | \
awk '/BEGIN CERT/,/END CERT/ { print $0 }' > backend-ca.pem

When debugging SSL handshakes:

echo "quit" | openssl s_client -connect backend:443 -status -debug -msg -state

Check HAProxy logs for SSL errors with tail -f /var/log/haproxy.log | grep ssl

SSL re-encryption adds CPU overhead. Consider:

  • Using EC certificates for faster handshakes
  • Implementing SSL session reuse with ssl-default-server-options no-sslv3 no-tls-tickets
  • Monitoring SSL handshake rates with echo "show info" | socat /var/run/haproxy.sock -

Many modern architectures require SSL termination at the load balancer while maintaining backend security. HAProxy provides powerful tools for this scenario, but the configuration requires careful attention to detail.

To implement this properly, we need to handle:

  • Frontend SSL termination
  • HTTP header manipulation
  • URL rewriting
  • Backend SSL re-encryption
  • Session stickiness

Here's a working configuration that addresses all requirements:


global
    log /dev/log local0
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    daemon
    tune.ssl.default-dh-param 2048

defaults
    log global
    mode http
    option httplog
    option dontlognull
    timeout connect 5000
    timeout client 50000
    timeout server 50000

frontend https-in
    bind *:443 ssl crt /etc/ssl/certs/frontend.pem
    mode http
    option forwardfor
    
    # Extract real client IP
    http-request set-header X-Forwarded-For %[src]
    
    # URL rewriting example
    acl is_legacy path_beg /oldpath
    http-request redirect code 301 location /newpath%[path] if is_legacy
    
    # Cookie-based session stickiness
    cookie SERVERID insert indirect nocache secure
    
    use_backend %[req.cook(SERVERID)]

backend secure_servers
    mode http
    balance roundrobin
    cookie SERVERID prefix
    server s1 192.168.1.10:443 ssl verify none check cookie s1
    server s2 192.168.1.11:443 ssl verify none check cookie s2
    
    # Re-encrypt traffic to backend
    option ssl-hello-chk
    http-request set-header X-Forwarded-Proto https if { ssl_fc }

SSL Termination and Re-encryption: The frontend handles SSL termination while the backend servers are configured with 'ssl' parameter for re-encryption.

Header Manipulation: We use http-request set-header directives to properly set X-Forwarded-For and X-Forwarded-Proto headers.

Session Stickiness: The cookie-based persistence ensures clients stick to the same backend server.

For more complex URL manipulation:


# Rewrite /api/v1/ to /v2/ while preserving query strings
acl is_v1 path_beg /api/v1/
http-request set-path /v2/%[path,regsub(^/api/v1/,/)] if is_v1
http-request replace-header Host (.*) v2.example.com if is_v1

When implementing SSL re-encryption:

  • Enable SSL session caching in global section: ssl-default-server-cache 200000
  • Consider using TLS tickets for reduced handshake overhead
  • Monitor CPU usage as SSL operations are computationally intensive

If you encounter issues:

  1. Check logs with tail -f /var/log/haproxy.log
  2. Verify SSL certificates with openssl s_client -connect backend:443
  3. Test header forwarding with curl -v -H "Host: example.com" https://haproxy