HAProxy SSL/TLS Configuration
Best practices for SSL/TLS termination in HAProxy including load balancing, modern security standards, and performance optimization.
Basic SSL Termination
Basic HTTPS frontend
Minimal SSL termination configuration:
global
log /dev/log local0
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
# SSL settings
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
default_backend web_servers
backend web_servers
balance roundrobin
server web1 192.168.1.10:80 check
server web2 192.168.1.11:80 checkCombined certificate file
HAProxy requires certificate and key in single PEM file:
# Combine certificate, key, and chain
cat example.com.crt example.com.key intermediate.crt > /etc/haproxy/certs/example.com.pem
# Set proper permissions
chmod 600 /etc/haproxy/certs/example.com.pem
chown haproxy:haproxy /etc/haproxy/certs/example.com.pemHTTP to HTTPS redirect
Force all traffic to HTTPS:
frontend http_frontend
bind *:80
redirect scheme https code 301 if !{ ssl_fc }Modern Security Configuration
TLS protocols and ciphers
Recommended SSL/TLS configuration for 2024+:
global
# Modern configuration (TLS 1.2 and 1.3 only)
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
# Server-side SSL configuration
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl-default-server-options ssl-min-ver TLSv1.2 no-tls-ticketsDH parameters
Generate and use strong Diffie-Hellman parameters:
# Generate DH params (one-time setup)
openssl dhparam -out /etc/haproxy/dhparams.pem 4096
# Add to global section
global
ssl-dh-param-file /etc/haproxy/dhparams.pemCurve preferences
Specify elliptic curve preferences:
global
# HAProxy 2.4+
ssl-default-bind-curves X25519:secp384r1:secp521r1HSTS and Security Headers
Complete security headers
Add security headers to HTTPS frontend:
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/
# HSTS with 2-year max-age
http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
# Additional security headers
http-response set-header X-Content-Type-Options "nosniff"
http-response set-header X-Frame-Options "SAMEORIGIN"
http-response set-header X-XSS-Protection "1; mode=block"
http-response set-header Referrer-Policy "strict-origin-when-cross-origin"
# Content Security Policy
http-response set-header Content-Security-Policy "default-src 'self' https:"
default_backend web_serversRemove server headers
Hide server version information:
frontend https_frontend
# Remove server identification
http-response del-header Server
http-response set-header Server "Secure Web Server"
# Remove X-Powered-By
http-response del-header X-Powered-ByOCSP Stapling
Enable OCSP stapling
HAProxy 2.2+ supports OCSP stapling:
# Fetch OCSP response manually
openssl ocsp -issuer intermediate.crt -cert example.com.crt \
-url http://ocsp.example.com -respout example.com.ocsp
# Add OCSP response to bind line
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem ocsp-update onAutomatic OCSP updates
Configure automatic OCSP response updates (HAProxy 2.2+):
global
# Enable OCSP auto-update
ocsp-update.mode on
ocsp-update.maxdelay 3600
ocsp-update.mindelay 300
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/ ocsp-update onMulti-Certificate SNI Configuration
Directory-based certificates
Load all certificates from directory (recommended):
# Place all combined PEM files in /etc/haproxy/certs/
# HAProxy automatically uses SNI to select correct cert
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/
# HAProxy selects certificate based on SNI
# example.com.pem for example.com
# api.example.com.pem for api.example.com
default_backend web_serversExplicit certificate binding
Specify multiple certificates explicitly:
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem crt /etc/haproxy/certs/api.example.com.pem
default_backend web_serversSNI-based routing
Route to different backends based on SNI:
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/
# Route based on hostname
use_backend api_backend if { ssl_fc_sni api.example.com }
use_backend web_backend if { ssl_fc_sni www.example.com }
default_backend web_backend
backend api_backend
balance roundrobin
server api1 192.168.1.20:8080 check
backend web_backend
balance roundrobin
server web1 192.168.1.10:80 check
server web2 192.168.1.11:80 checkClient Certificate Authentication (mTLS)
Require client certificates
Enable mutual TLS authentication:
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/ ca-file /etc/haproxy/ca/client-ca.pem verify required
# Pass client cert info to backend
http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)]
http-request set-header X-SSL-Client-DN %{+Q}[ssl_c_s_dn]
http-request set-header X-SSL-Client-Verify %[ssl_c_verify]
default_backend web_serversOptional client certificates
Make client certs optional, check in backend:
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/ ca-file /etc/haproxy/ca/client-ca.pem verify optional
# Only process if client cert is valid
acl client_cert_valid ssl_c_verify 0
http-request set-header X-SSL-Client-Verified true if client_cert_valid
default_backend web_serversCRL checking
Validate client certificates against CRL:
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/ ca-file /etc/haproxy/ca/client-ca.pem crl-file /etc/haproxy/ca/crl.pem verify requiredBackend SSL/TLS Connections
SSL to backend servers
# Terminate SSL at HAProxy, re-encrypt to backend
backend secure_backend
balance roundrobin
# Connect to backend via SSL
server web1 192.168.1.10:443 ssl verify required ca-file /etc/haproxy/ca/backend-ca.pem check
server web2 192.168.1.11:443 ssl verify required ca-file /etc/haproxy/ca/backend-ca.pem checkUse when backend servers require encrypted connections and certificate validation.
SSL without verification
backend backend_no_verify
balance roundrobin
# SSL to backend without certificate verification (not recommended for production)
server web1 192.168.1.10:443 ssl verify none check
server web2 192.168.1.11:443 ssl verify none checkUseful for testing or trusted internal networks, but verify required is preferred.
Backend client certificate authentication
backend mtls_backend
balance roundrobin
# Present client certificate to backend
server web1 192.168.1.10:443 ssl verify required ca-file /etc/haproxy/ca/backend-ca.pem crt /etc/haproxy/client-certs/haproxy-client.pem checkHAProxy presents a client certificate when connecting to backend servers requiring mTLS.
Health Checks with TLS
HTTP health checks over SSL
backend web_backend
balance roundrobin
# Health check configuration
option httpchk GET /health
http-check expect status 200
# Servers with SSL health checks
server web1 192.168.1.10:443 ssl verify none check check-ssl
server web2 192.168.1.11:443 ssl verify none check check-sslUse check-ssl to perform health checks over SSL/TLS connections.
SSL health check with SNI
backend web_backend
balance roundrobin
option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
# Include SNI in health checks
server web1 192.168.1.10:443 ssl verify none check check-ssl sni str(example.com)
server web2 192.168.1.11:443 ssl verify none check check-ssl sni str(example.com)Send SNI hostname during health checks for virtual host environments.
TCP health checks for SSL
backend web_backend
mode tcp
balance roundrobin
# Simple TCP connection check
option tcp-check
server web1 192.168.1.10:443 check
server web2 192.168.1.11:443 checkBasic TCP connectivity check without SSL handshake inspection.
SSL Passthrough (No Termination)
TCP mode passthrough
Forward encrypted traffic to backend without decryption:
frontend ssl_passthrough
mode tcp
bind *:443
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
# Route based on SNI
use_backend backend_example if { req_ssl_sni -i example.com }
use_backend backend_api if { req_ssl_sni -i api.example.com }
backend backend_example
mode tcp
balance roundrobin
server web1 192.168.1.10:443 check
server web2 192.168.1.11:443 check
backend backend_api
mode tcp
balance roundrobin
server api1 192.168.1.20:443 checkLet's Encrypt Certificate Management
Obtain certificates with certbot
# Install certbot
apt-get install certbot
# Obtain certificate using webroot
certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
# Or standalone mode (stops HAProxy temporarily)
systemctl stop haproxy
certbot certonly --standalone -d example.com -d www.example.com
systemctl start haproxyWebroot method allows certificate issuance without stopping HAProxy.
Convert to HAProxy format
# Combine Let's Encrypt files into HAProxy PEM format
cat /etc/letsencrypt/live/example.com/fullchain.pem \
/etc/letsencrypt/live/example.com/privkey.pem \
> /etc/haproxy/certs/example.com.pem
# Set permissions
chmod 600 /etc/haproxy/certs/example.com.pem
chown haproxy:haproxy /etc/haproxy/certs/example.com.pem
# Reload HAProxy
systemctl reload haproxyHAProxy requires certificate and private key in single file.
Automatic renewal script
# Create renewal hook script: /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh
#!/bin/bash
DOMAIN="example.com"
cat /etc/letsencrypt/live/${DOMAIN}/fullchain.pem \
/etc/letsencrypt/live/${DOMAIN}/privkey.pem \
> /etc/haproxy/certs/${DOMAIN}.pem
chmod 600 /etc/haproxy/certs/${DOMAIN}.pem
systemctl reload haproxy
# Make executable
chmod +x /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh
# Test renewal
certbot renew --dry-runDeploy hook automatically updates HAProxy certificates after renewal.
Complete Production Configuration
Hardened HAProxy SSL config
Complete example with all best practices:
global
log /dev/log local0
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
# SSL/TLS Settings
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dhparams.pem
# OCSP
ocsp-update.mode on
ocsp-update.maxdelay 3600
# Tuning
tune.ssl.default-dh-param 4096
maxconn 4096
defaults
log global
mode http
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout connect 5000
timeout client 50000
timeout server 50000
# HTTP redirect to HTTPS
frontend http_frontend
bind *:80
redirect scheme https code 301 if !{ ssl_fc }
# HTTPS frontend
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 ocsp-update on
# Security Headers
http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
http-response set-header X-Content-Type-Options "nosniff"
http-response set-header X-Frame-Options "SAMEORIGIN"
http-response set-header X-XSS-Protection "1; mode=block"
http-response set-header Referrer-Policy "strict-origin-when-cross-origin"
# Remove server identification
http-response del-header Server
http-response del-header X-Powered-By
# Logging
capture request header Host len 64
capture request header User-Agent len 128
# Routing
use_backend api_backend if { path_beg /api }
default_backend web_backend
backend web_backend
balance roundrobin
option httpchk GET /health
http-check expect status 200
server web1 192.168.1.10:80 check
server web2 192.168.1.11:80 check
backend api_backend
balance roundrobin
option httpchk GET /api/health
http-check expect status 200
server api1 192.168.1.20:8080 check
server api2 192.168.1.21:8080 check
# Stats interface
listen stats
bind *:8404 ssl crt /etc/haproxy/certs/stats.pem
stats enable
stats uri /stats
stats refresh 30s
stats auth admin:yourpasswordPerformance Optimization
HTTP/2 and ALPN
Enable HTTP/2 with ALPN negotiation:
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1SSL session cache
Tune SSL session caching:
global
tune.ssl.cachesize 100000
tune.ssl.lifetime 600
tune.ssl.maxrecord 1419 # Optimized for MTUConnection limits
Optimize connection handling:
global
maxconn 4096
maxsslconn 4096
tune.ssl.default-dh-param 2048Testing and Validation
Test configuration
Validate HAProxy config before reload:
# Test configuration syntax
haproxy -c -f /etc/haproxy/haproxy.cfg
# Reload if successful
systemctl reload haproxy
# Or graceful reload
haproxy -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid)Check SSL configuration
Verify SSL settings:
# Test TLS connection
openssl s_client -connect example.com:443 -servername example.com
# Test HTTP/2
curl -I --http2 https://example.com
# Test specific TLS version
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3SSL Labs test
Comprehensive SSL analysis:
# Visit: https://www.ssllabs.com/ssltest/
# Target: A+ rating with modern configurationSee Also
Important Notes
HAProxy requires certificate, private key, and chain in a single PEM file. Order: cert → key → intermediates.
HAProxy uses SNI to select certificates automatically when loading from directory. Ensure clients support SNI.
Use `systemctl reload haproxy` for zero-downtime certificate updates. HAProxy transfers active connections to new process.
Requires HAProxy 2.2+ for automatic updates. Earlier versions need manual OCSP response management.
Requires HAProxy 1.8+ and OpenSSL 1.0.2+. Use `alpn h2,http/1.1` on bind line.
For mTLS, use verify required, verify optional, or verify optional_no_ca depending on use case.
PEM files should be readable only by haproxy user (chmod 600, chown haproxy).
HAProxy is extremely efficient at SSL termination. Can handle thousands of concurrent SSL connections on modest hardware.
Official HAProxy docs: haproxy.org/documentation.html
SSL Configuration: cbonte.github.io/haproxy-dconv/configuration-1.9.html#5.1-ssl