HAProxy Advanced Configuration: SSL Termination

HAProxy is a powerful, open-source load balancer and reverse proxy renowned for its performance and flexibility. SSL/TLS termination in HAProxy offloads encryption overhead from backend servers, enabling efficient HTTPS traffic handling. This guide covers advanced SSL termination configurations, multi-certificate management, access control lists (ACLs), session persistence, and comprehensive logging.

Table of Contents

  1. SSL/TLS Termination Basics
  2. Installation and Setup
  3. Single Certificate Configuration
  4. Multi-Certificate Setup
  5. ACLs and Routing
  6. Maps for Dynamic Routing
  7. Stick Tables and Session Persistence
  8. Connection Limits
  9. Advanced Logging
  10. Health Checks
  11. Performance Tuning
  12. Troubleshooting

SSL/TLS Termination Basics

SSL/TLS termination at HAProxy provides several benefits:

  • Reduces backend server CPU load (no encryption overhead)
  • Centralizes certificate management
  • Enables advanced routing based on SSL/TLS properties
  • Simplifies backend server configuration
  • Facilitates certificate rotation and updates

HAProxy decrypts incoming HTTPS connections and communicates with backends over HTTP or HTTPS.

Installation and Setup

Install HAProxy on Debian/Ubuntu:

sudo apt update
sudo apt install haproxy

Install on RHEL/CentOS:

sudo yum install haproxy

Verify installation:

haproxy -v

Check the default configuration:

cat /etc/haproxy/haproxy.cfg

Start the service:

sudo systemctl enable haproxy
sudo systemctl start haproxy

Single Certificate Configuration

Create a basic HAProxy configuration with SSL termination:

sudo tee /etc/haproxy/haproxy.cfg > /dev/null <<'EOF'
global
    log stdout local0
    log stdout 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
    tune.ssl.ciphers HIGH:!aNULL:!MD5
    tune.ssl.options ssl-default-bind-ciphers ssl-default-server-ciphers ssl-min-ver TLSv1.2

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

listen stats
    bind *:8404
    mode http
    stats enable
    stats uri /stats
    stats refresh 30s
    stats show-legends
    stats admin if TRUE

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    bind *:80
    
    mode http
    option httpclose
    option forwardfor except 127.0.0.1
    
    http-request redirect scheme https code 301 if !{ ssl_fc }
    
    default_backend web_servers

backend web_servers
    balance roundrobin
    mode http
    option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
    
    server web1 192.168.1.100:80 check inter 2000 fall 3 rise 2
    server web2 192.168.1.101:80 check inter 2000 fall 3 rise 2
    server web3 192.168.1.102:80 check inter 2000 fall 3 rise 2 backup
EOF

Create the certificate file by combining the certificate and private key:

sudo mkdir -p /etc/haproxy/certs
sudo cat /path/to/certificate.crt /path/to/private.key > /etc/haproxy/certs/example.com.pem
sudo chmod 600 /etc/haproxy/certs/example.com.pem

Validate configuration:

sudo haproxy -f /etc/haproxy/haproxy.cfg -c

Reload configuration:

sudo systemctl reload haproxy

Multi-Certificate Setup

Handle multiple domains with SNI (Server Name Indication):

sudo tee /etc/haproxy/haproxy.cfg > /dev/null <<'EOF'
global
    log stdout local0
    stats socket /run/haproxy/admin.sock mode 660 level admin

defaults
    log     global
    mode    http
    timeout connect 5000
    timeout client  50000
    timeout server  50000

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem crt /etc/haproxy/certs/api.example.com.pem crt /etc/haproxy/certs/blog.example.com.pem
    bind *:80
    
    mode http
    option httpclose
    option forwardfor
    
    http-request redirect scheme https code 301 if !{ ssl_fc }
    
    acl is_example_com hdr(host) -i example.com www.example.com
    acl is_api hdr(host) -i api.example.com
    acl is_blog hdr(host) -i blog.example.com
    
    use_backend backend_web if is_example_com
    use_backend backend_api if is_api
    use_backend backend_blog if is_blog
    default_backend backend_web

backend backend_web
    balance roundrobin
    server web1 192.168.1.100:8000 check
    server web2 192.168.1.101:8000 check

backend backend_api
    balance roundrobin
    server api1 192.168.1.110:8080 check
    server api2 192.168.1.111:8080 check

backend backend_blog
    balance roundrobin
    server blog1 192.168.1.120:3000 check
EOF

Create certificate files:

sudo mkdir -p /etc/haproxy/certs

# Combine certificate and key for each domain
sudo cat /etc/ssl/certs/example.com.crt /etc/ssl/private/example.com.key > /etc/haproxy/certs/example.com.pem
sudo cat /etc/ssl/certs/api.example.com.crt /etc/ssl/private/api.example.com.key > /etc/haproxy/certs/api.example.com.pem
sudo cat /etc/ssl/certs/blog.example.com.crt /etc/ssl/private/blog.example.com.key > /etc/haproxy/certs/blog.example.com.pem

sudo chown -R haproxy:haproxy /etc/haproxy/certs
sudo chmod 600 /etc/haproxy/certs/*.pem

ACLs and Routing

Use ACLs (Access Control Lists) for advanced routing decisions:

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    bind *:80
    
    mode http
    option httpclose
    option forwardfor
    
    # Define ACLs
    acl is_api hdr(host) -i api.example.com
    acl is_admin path_beg /admin
    acl is_static path_beg /static /images /css /js
    acl is_mobile hdr(user-agent) -i smartphone mobile
    acl high_traffic src_conn_rate gt 100
    
    # Deny high traffic sources
    http-request deny if high_traffic
    
    # Route traffic
    use_backend backend_api if is_api
    use_backend backend_static if is_static
    use_backend backend_admin if is_admin is_admin
    use_backend backend_mobile if is_mobile
    default_backend backend_web

backend backend_api
    balance roundrobin
    server api1 192.168.1.110:8080 check

backend backend_static
    balance roundrobin
    server cdn1 192.168.1.130:80 check

backend backend_admin
    balance roundrobin
    server admin1 192.168.1.140:8000 check

backend backend_mobile
    balance roundrobin
    server mobile1 192.168.1.150:3000 check

backend backend_web
    balance roundrobin
    server web1 192.168.1.100:8000 check
    server web2 192.168.1.101:8000 check

Maps for Dynamic Routing

Use maps to manage routing rules via external files:

Create a routing map file /etc/haproxy/maps/backends.map:

example.com                  backend_web
api.example.com              backend_api
blog.example.com             backend_blog
cdn.example.com              backend_static
admin.example.com            backend_admin

Reference the map in HAProxy configuration:

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    bind *:80
    
    mode http
    option httpclose
    option forwardfor
    
    http-request redirect scheme https code 301 if !{ ssl_fc }
    
    use_backend %[req.hdr(host),lower,map(/etc/haproxy/maps/backends.map,backend_web)]

backend backend_web
    balance roundrobin
    server web1 192.168.1.100:8000 check

backend backend_api
    balance roundrobin
    server api1 192.168.1.110:8080 check

backend backend_blog
    balance roundrobin
    server blog1 192.168.1.120:3000 check

backend backend_static
    balance roundrobin
    server cdn1 192.168.1.130:80 check

backend backend_admin
    balance roundrobin
    server admin1 192.168.1.140:8000 check

Update maps without restarting HAProxy via the admin socket:

echo "example.com backend_api" | socat - /run/haproxy/admin.sock

Stick Tables and Session Persistence

Implement sticky sessions using stick tables:

global
    stats socket /run/haproxy/admin.sock mode 660 level admin

defaults
    mode http
    timeout connect 5000
    timeout client  50000
    timeout server  50000

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    bind *:80
    
    mode http
    option httpclose
    option forwardfor
    
    http-request redirect scheme https code 301 if !{ ssl_fc }
    
    stick-table type string len 32 size 100k expire 30m
    stick on cookie(JSESSIONID)
    
    default_backend backend_web

backend backend_web
    balance roundrobin
    
    stick-table type string len 32 size 100k expire 30m
    stick on cookie(JSESSIONID)
    
    server web1 192.168.1.100:8000 check
    server web2 192.168.1.101:8000 check
    server web3 192.168.1.102:8000 check

Use IP-based stickiness:

frontend https_in
    bind *:443 ssl
    
    stick-table type ip size 100k expire 30m
    stick on src
    
    default_backend backend_web

backend backend_web
    balance roundrobin
    server web1 192.168.1.100:8000 check
    server web2 192.168.1.101:8000 check

Connection Limits

Implement per-client connection limits:

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    
    stick-table type ip size 100k expire 1m store http_req_rate(10s)
    
    http-request track-sc0 src
    http-request deny if { sc_http_req_rate(0) gt 100 }
    
    default_backend backend_web

backend backend_web
    balance roundrobin
    server web1 192.168.1.100:8000 check maxconn 1000
    server web2 192.168.1.101:8000 check maxconn 1000

Limit concurrent connections:

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem maxconn 10000
    
    default_backend backend_web

backend backend_web
    balance roundrobin
    server web1 192.168.1.100:8000 check maxconn 500
    server web2 192.168.1.101:8000 check maxconn 500

Advanced Logging

Configure detailed request logging:

global
    log 127.0.0.1 local0 debug
    log 127.0.0.1 local1 notice

defaults
    log     global
    mode    http
    option  httplog
    option  http-server-close
    option  denylogging
    
    log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"

The log format breaks down as:

  • %ci:%cp - Client IP:port
  • %tr - Request timestamp
  • %ft - Frontend name
  • %b/%s - Backend/server name
  • %TR/%Tw/%Tc/%Tr/%Ta - Timings (request, queue, connect, response, total)
  • %ST - HTTP status code
  • %B - Response size
  • %CC/%CS - Cookie counts
  • %tsc - Termination state
  • %{+Q}r - Request line

View logs:

tail -f /var/log/haproxy.log

Send logs to syslog:

sudo tee /etc/rsyslog.d/49-haproxy.conf > /dev/null <<'EOF'
:programname, isequal, "haproxy" /var/log/haproxy.log
& stop
EOF

sudo systemctl restart rsyslog

Health Checks

Configure advanced health checks:

backend backend_web
    balance roundrobin
    option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
    http-check expect status 200
    
    server web1 192.168.1.100:8000 check inter 2000 fall 3 rise 2 weight 1
    server web2 192.168.1.101:8000 check inter 2000 fall 3 rise 2 weight 2
    server web3 192.168.1.102:8000 check inter 2000 fall 3 rise 2 backup

backend backend_api
    balance roundrobin
    option tcp-check
    tcp-check connect port 8080
    
    server api1 192.168.1.110:8080 check inter 5000 fall 2 rise 1

Performance Tuning

Optimize HAProxy for high-traffic scenarios:

global
    tune.maxconn 200000
    tune.maxconnrate 4000
    tune.maxsslconn 100000
    tune.ssl.cachesize 1000000
    tune.ssl.lifetime 600
    tune.http.maxhdr 101
    tune.http.uri-max 8192
    tune.bufsize 32768

defaults
    mode http
    timeout connect 5s
    timeout client 50s
    timeout server 50s
    timeout tunnel 1h
    timeout http-keep-alive 1s
    option http-server-close
    option dup-cookie-names

Troubleshooting

Check HAProxy status:

sudo systemctl status haproxy
sudo tail -f /var/log/haproxy.log

Validate configuration:

sudo haproxy -f /etc/haproxy/haproxy.cfg -c

Monitor stats in real-time:

watch -n 1 'echo "show stat" | socat - /run/haproxy/admin.sock'

Test SSL certificate:

openssl s_client -connect localhost:443 -servername example.com

Check certificate expiration:

openssl x509 -in /etc/haproxy/certs/example.com.pem -noout -dates

Conclusion

HAProxy delivers enterprise-grade SSL/TLS termination with advanced features for complex load balancing scenarios. Its powerful ACLs, dynamic routing through maps, session persistence with stick tables, and comprehensive logging capabilities make it ideal for high-traffic production environments. Combined with sophisticated health checks and performance tuning options, HAProxy provides the foundation for reliable, scalable infrastructure.