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
- SSL/TLS Termination Basics
- Installation and Setup
- Single Certificate Configuration
- Multi-Certificate Setup
- ACLs and Routing
- Maps for Dynamic Routing
- Stick Tables and Session Persistence
- Connection Limits
- Advanced Logging
- Health Checks
- Performance Tuning
- 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.


