Back to Guides
Web ServersIntermediate

HAProxy SSL Certificate Configuration

The complete guide to configuring SSL/TLS in HAProxy - from basic termination to production hardening and A+ SSL Labs rating.

12-15 min read
HAProxy SSL Certificate Configuration Guide

1. What is HAProxy?

HAProxy (High Availability Proxy) is one of the most popular open-source load balancers and reverse proxies, trusted by high-traffic websites like GitHub, Stack Overflow, Reddit, and Tumblr. Written in C for maximum performance, it can handle millions of concurrent connections with minimal resource usage.

Key Capabilities:

  • Layer 4 (TCP) and Layer 7 (HTTP) load balancing
  • SSL/TLS termination with high-performance crypto offloading
  • Health checking with automatic backend failover
  • Content-based routing via ACLs (Access Control Lists)
  • Rate limiting and connection throttling
  • Hot reload - update configuration without dropping connections

This guide focuses specifically on SSL/TLS certificate configuration in HAProxy. For general HAProxy setup and load balancing concepts, refer to the official documentation linked above.

2. Version Requirements

This guide targets HAProxy 2.4+ (LTS). Some features require specific versions:

FeatureMinimum VersionNotes
Basic SSL/TLS termination1.5+Native SSL support added
TLS 1.3 support1.8+Requires OpenSSL 1.1.1+
ssl-min-ver / ssl-max-ver1.8+Cleaner than force-* options
ocsp-update on (automatic OCSP)2.4+Auto-fetches OCSP responses
ssl-default-bind-ciphersuites1.8+TLS 1.3 cipher control (requires OpenSSL 1.1.1)
HTTP/2 (alpn h2)1.6+ALPN negotiation

Check your HAProxy version:

haproxy -v
# Example output: HAProxy version 2.8.3-1

Recommendation: Use HAProxy 2.4 LTS or 2.8 LTS for production. These versions receive long-term security updates and include all modern SSL features.

3. Quick Start: Minimal vs A+ Config

Choose based on your needs: Minimal for quick compatibility testing, A+ Strict for production security.

Minimal Starter (Compatibility)

Works with older clients, quick to deploy. Upgrade to A+ when stable.

frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/domain.pem
    default_backend web_servers

backend web_servers
    server web1 192.168.1.10:80 check
  • • Uses system defaults (TLS 1.0-1.3)
  • • Wide client compatibility
  • • SSL Labs grade: B or C

A+ Strict (Production)

Maximum security. Modern clients only (TLS 1.2+).

global
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
    tune.ssl.default-dh-param 2048

frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/domain.pem alpn h2,http/1.1
    http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    default_backend web_servers

backend web_servers
    server web1 192.168.1.10:80 check
  • • TLS 1.2+ only, strong ciphers
  • • HSTS enabled, HTTP/2 ready
  • • SSL Labs grade: A+

When to use each:

  • Minimal:Internal services, development environments, legacy client support, quick testing
  • A+ Strict:Public-facing websites, APIs, compliance requirements (PCI DSS, HIPAA), security-conscious deployments

4. SSL/TLS Modes in HAProxy

HAProxy supports three SSL/TLS modes, each serving different use cases:

ModeDescriptionUse Case
SSL TerminationHAProxy decrypts traffic, sends plain HTTP to backendMost common, offloads SSL from app servers
SSL PassthroughHAProxy passes encrypted traffic through unchangedEnd-to-end encryption required, can't inspect traffic
SSL Re-encryptionHAProxy decrypts, then re-encrypts to backendNeed to inspect traffic AND encrypt to backend

Traffic Flow Diagram:

SSL TERMINATION:
[Client] --HTTPS--> [HAProxy] --HTTP--> [Backend]
SSL PASSTHROUGH:
[Client] --HTTPS--> [HAProxy] --HTTPS--> [Backend]
(no decryption)
SSL RE-ENCRYPTION:
[Client] --HTTPS--> [HAProxy] --HTTPS--> [Backend]
(decrypt → re-encrypt)

5. Certificate Format Requirements

Critical: HAProxy requires a specific PEM format

-----BEGIN CERTIFICATE-----
(Your server certificate)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
(Intermediate certificate)
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
(Your private key)
-----END PRIVATE KEY-----

Key Points:

  • Single file containing cert + chain + key (concatenated)
  • Order matters: server cert first, then intermediates, then key
  • No root certificate needed (browsers have it)
  • File permissions: 600 (owner read/write only)

Create combined PEM file:

cat domain.crt intermediate.crt domain.key > /etc/haproxy/certs/domain.pem
chmod 600 /etc/haproxy/certs/domain.pem

Don't include the root certificate. HAProxy will work, but it wastes bandwidth and some clients may reject chains that include the root.

6. Basic SSL Termination Configuration

Minimal working configuration to get HTTPS running:

global
    # SSL tuning
    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
    
    # DH parameters (generate with: openssl dhparam -out /etc/haproxy/dhparam.pem 2048)
    tune.ssl.default-dh-param 2048

frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/domain.pem
    
    # Redirect HTTP to HTTPS
    http-request redirect scheme https unless { ssl_fc }
    
    default_backend web_servers

frontend http_front
    bind *:80
    http-request redirect scheme https code 301

backend web_servers
    balance roundrobin
    server web1 192.168.1.10:80 check
    server web2 192.168.1.11:80 check

Key Directives Explained:

  • bind *:443 ssl crt - Listen on 443 with SSL, specify cert path
  • ssl-min-ver TLSv1.2 - Minimum TLS version (disable TLS 1.0/1.1)
  • no-tls-tickets - Disable session tickets for forward secrecy
  • http-request redirect scheme https - Force HTTPS

7. Multiple Certificates with SNI

For hosting multiple domains on the same IP address:

Directory-based (HAProxy auto-loads all .pem files):

frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/
    
    # HAProxy auto-loads all .pem files from directory
    # Matches certificate to hostname via SNI

File Naming Convention:

/etc/haproxy/certs/
├── example.com.pem
├── api.example.com.pem
└── staging.example.com.pem

Or with explicit crt-list mapping:

bind *:443 ssl crt /etc/haproxy/certs/default.pem crt-list /etc/haproxy/certs/crt-list.txt

# crt-list.txt format:
# /path/to/cert.pem [sni filter] [options]
/etc/haproxy/certs/example.com.pem example.com
/etc/haproxy/certs/api.pem api.example.com

8. SSL Passthrough Configuration

When you can't or won't terminate SSL at HAProxy:

frontend https_passthrough
    bind *:443
    mode tcp
    
    # Use SNI to route to correct backend
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }
    
    use_backend backend_api if { req_ssl_sni -i api.example.com }
    default_backend backend_web

backend backend_web
    mode tcp
    server web1 192.168.1.10:443 check

backend backend_api
    mode tcp
    server api1 192.168.1.20:443 check

Key Points:

  • Must use mode tcp (not http)
  • Can still route based on SNI
  • Cannot inspect or modify HTTP headers
  • Backend servers handle SSL termination

9. SSL Re-encryption (Backend SSL)

Decrypt at HAProxy, re-encrypt to backend:

frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/frontend.pem
    default_backend secure_servers

backend secure_servers
    balance roundrobin
    
    # Enable SSL to backend
    server web1 192.168.1.10:443 ssl verify required ca-file /etc/haproxy/ca-bundle.crt check
    server web2 192.168.1.11:443 ssl verify required ca-file /etc/haproxy/ca-bundle.crt check
OptionDescription
sslEnable SSL to backend
verify noneDon't verify backend cert (NOT recommended)
verify requiredVerify backend cert against CA
ca-fileCA bundle for verification
crtClient cert for mTLS to backend

Never use verify none in production. It defeats the purpose of SSL and enables MITM attacks between HAProxy and your backend.

10. Achieving SSL Labs A+ Grade

Production-ready configuration for maximum security rating:

global
    # Modern cipher configuration
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    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-options ssl-min-ver TLSv1.2 no-tls-tickets
    
    ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    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-tickets
    
    tune.ssl.default-dh-param 2048

frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/domain.pem alpn h2,http/1.1
    
    # HSTS header (required for A+)
    http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    
    # Additional security headers
    http-response set-header X-Frame-Options DENY
    http-response set-header X-Content-Type-Options nosniff
    http-response set-header X-XSS-Protection "1; mode=block"
    
    default_backend web_servers

A+ Requirements Checklist:

  • TLS 1.2 minimum (TLS 1.3 preferred)
  • Strong cipher suites only (no RC4, no 3DES, no CBC)
  • Forward secrecy (ECDHE/DHE key exchange)
  • HSTS header with long max-age
  • No known vulnerabilities
  • Valid certificate chain

11. OCSP Stapling

Improve handshake performance by stapling OCSP responses:

Automatic OCSP updates (HAProxy 2.4+):

frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/domain.pem alpn h2,http/1.1 ocsp-update on

Or with explicit OCSP response file:

# Generate OCSP response manually
openssl ocsp -issuer intermediate.crt -cert domain.crt -url http://ocsp.ca.com -respout domain.ocsp

# Reference in HAProxy
bind *:443 ssl crt /etc/haproxy/certs/domain.pem ocsp-response /etc/haproxy/certs/domain.ocsp

Note: HAProxy 2.4+ supports automatic OCSP updates with ocsp-update on

12. Client Certificate Authentication (mTLS)

Require client certificates for mutual TLS authentication:

frontend https_mtls
    bind *:443 ssl crt /etc/haproxy/certs/server.pem ca-file /etc/haproxy/certs/client-ca.crt verify required
    
    # Pass client cert info to backend
    http-request set-header X-SSL-Client-DN %{+Q}[ssl_c_s_dn]
    http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)]
    http-request set-header X-SSL-Client-Verify %[ssl_c_verify]
    
    default_backend secure_api

backend secure_api
    server api1 192.168.1.10:8080 check
OptionDescription
verify noneDon't request client cert
verify optionalRequest but don't require
verify requiredRequire valid client cert
ca-fileCA that signed client certs
crl-fileCertificate Revocation List

13. Let's Encrypt / ACME Automation

Using certbot with HAProxy:

Obtain certificate (standalone mode):

# Install certbot
apt install certbot

# Obtain certificate (standalone mode - stops HAProxy briefly)
certbot certonly --standalone -d example.com -d www.example.com

# Combine for HAProxy format
cat /etc/letsencrypt/live/example.com/fullchain.pem /etc/letsencrypt/live/example.com/privkey.pem > /etc/haproxy/certs/example.com.pem

# Reload HAProxy
systemctl reload haproxy

Automated renewal script (/etc/letsencrypt/renewal-hooks/deploy/haproxy.sh):

#!/bin/bash
DOMAIN="example.com"
HAPROXY_CERT="/etc/haproxy/certs/${DOMAIN}.pem"

cat /etc/letsencrypt/live/${DOMAIN}/fullchain.pem /etc/letsencrypt/live/${DOMAIN}/privkey.pem > ${HAPROXY_CERT}

chmod 600 ${HAPROXY_CERT}
systemctl reload haproxy

HTTP-01 Challenge through HAProxy:

frontend http_front
    bind *:80
    
    # Allow ACME challenges through
    acl is_acme path_beg /.well-known/acme-challenge/
    use_backend acme_backend if is_acme
    
    # Redirect everything else to HTTPS
    http-request redirect scheme https code 301 unless is_acme

backend acme_backend
    server certbot 127.0.0.1:8888

14. Common Errors and Troubleshooting

"unable to load SSL certificate"

Causes:

  • • Wrong file permissions (need 600)
  • • Certificate and key don't match
  • • Corrupt or malformed PEM file
  • • Missing intermediate certificates

Debug - Check cert and key match:

openssl x509 -noout -modulus -in cert.pem | openssl md5
openssl rsa -noout -modulus -in key.pem | openssl md5
# Output should match

"SSL handshake failure"

Causes:

  • • TLS version mismatch (client too old)
  • • No common cipher suites
  • • Certificate chain incomplete

Debug:

# Test connection with specific TLS version
openssl s_client -connect example.com:443 -tls1_2

# Check supported ciphers
openssl s_client -connect example.com:443 -cipher 'ALL'

"certificate verify failed"

Causes:

  • • Missing intermediate certificate
  • • Expired certificate
  • • Wrong certificate for hostname

Debug:

# Check full chain
openssl s_client -connect example.com:443 -showcerts

# Verify chain locally
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt -untrusted intermediate.crt server.crt

"no shared cipher"

Causes:

  • • HAProxy cipher config too restrictive for client
  • • Client only supports deprecated ciphers

Fix: Check your cipher configuration against client requirements. Consider adding fallback ciphers for older clients if needed.

15. Configuration Validation

Before reloading:

# Check configuration syntax
haproxy -c -f /etc/haproxy/haproxy.cfg

# Test with verbose output
haproxy -c -V -f /etc/haproxy/haproxy.cfg

Runtime checks:

# Check socket stats
echo "show ssl cert" | socat stdio /var/run/haproxy/admin.sock

# Show certificate details
echo "show ssl cert /etc/haproxy/certs/domain.pem" | socat stdio /var/run/haproxy/admin.sock

16. Quick Reference: Essential Commands

TaskCommand
Check config syntaxhaproxy -c -f /etc/haproxy/haproxy.cfg
Reload without downtimesystemctl reload haproxy
Check cert expirationopenssl x509 -enddate -noout -in cert.pem
Verify cert/key matchopenssl x509 -noout -modulus -in cert.pem | openssl md5
Test SSL connectionopenssl s_client -connect host:443
View cert chainopenssl s_client -connect host:443 -showcerts
Create combined PEMcat cert.pem chain.pem key.pem > combined.pem

17. Frequently Asked Questions

Related Resources