This site is an archive and is no longer updated.
    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
    LinkedInX
    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