SSL/TLSHAProxy · Let's Encrypt12 min read

HAProxy SSL Termination with Let's Encrypt: Step-by-Step Setup (2025)

Set up free HTTPS on HAProxy using Let's Encrypt certificates with Certbot. Includes auto-renewal, TLS 1.3, HTTP/2, OCSP stapling, and HSTS. Complete setup in under 20 minutes.

PS
PentaSynth Team
January 8, 2025 · Updated March 2025

How HAProxy SSL Termination Works

In SSL termination, HAProxy decrypts incoming HTTPS connections from clients, then forwards unencrypted (or re-encrypted) traffic to your backend servers. This means:

  • Your application servers don't need to manage SSL certificates
  • SSL processing is centralized and easier to audit
  • HAProxy can inspect HTTP headers (impossible with SSL passthrough)
  • You get significant CPU savings on your application servers

The difference from SSL passthrough (where HAProxy forwards the encrypted bytes without decrypting) is that with termination, HAProxy holds the private key and does the decryption work.

Step 1: Install HAProxy

# Ubuntu 22.04 / 24.04
apt-get install --no-install-recommends software-properties-common
add-apt-repository ppa:vbernat/haproxy-2.9
apt-get install haproxy=2.9.*

# Verify
haproxy -v

Step 2: Install Certbot

# Ubuntu
apt-get install certbot

# RHEL / Amazon Linux 2
amazon-linux-extras install epel
yum install certbot

Step 3: Obtain Your First Let's Encrypt Certificate

HAProxy needs to listen on port 80 for the ACME HTTP challenge. We'll use thestandalone mode first (before HAProxy is running), then switch to a renewal hook that stops/starts HAProxy:

# Stop HAProxy temporarily for initial cert issuance
systemctl stop haproxy

# Obtain certificate (replace with your domain)
certbot certonly --standalone \
    -d example.com \
    -d www.example.com \
    --email you@example.com \
    --agree-tos \
    --non-interactive

# Certificates will be at:
# /etc/letsencrypt/live/example.com/fullchain.pem
# /etc/letsencrypt/live/example.com/privkey.pem

Step 4: Combine Certificate Files for HAProxy

HAProxy requires a single .pem file containing both the certificate chain and private key:

# Create the combined PEM file
cat /etc/letsencrypt/live/example.com/fullchain.pem \
    /etc/letsencrypt/live/example.com/privkey.pem \
    > /etc/haproxy/certs/example.com.pem

# Secure it
chmod 600 /etc/haproxy/certs/example.com.pem

# Create the directory if needed
mkdir -p /etc/haproxy/certs

Step 5: Configure HAProxy with SSL

global
    log /dev/log local0
    maxconn 50000
    user haproxy
    group haproxy
    daemon

    # TLS hardening
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-sslv3 no-tls-tickets
    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

    # SSL cache for session resumption
    tune.ssl.cachesize 100000
    tune.ssl.lifetime 300

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    option  forwardfor
    option  http-server-close
    timeout connect 5s
    timeout client  30s
    timeout server  30s

# ACME challenge frontend (HTTP only, for Certbot renewal)
frontend http_frontend
    bind *:80
    # Allow Let's Encrypt ACME challenge 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

# HTTPS frontend
frontend https_frontend
    bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1

    # HSTS - force browsers to always use HTTPS
    http-response set-header Strict-Transport-Security \
        "max-age=63072000; includeSubDomains; preload"

    # Security headers
    http-response set-header X-Frame-Options "SAMEORIGIN"
    http-response set-header X-Content-Type-Options "nosniff"
    http-response del-header Server

    # Pass real IP to backends
    http-request set-header X-Forwarded-Proto https

    default_backend web_servers

# ACME challenge backend (simple HTTP server on port 8888)
backend acme_backend
    server localhost 127.0.0.1:8888

# Your actual backend servers
backend web_servers
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200
    server app1 10.0.0.1:8080 check inter 2s fall 3 rise 2
    server app2 10.0.0.2:8080 check inter 2s fall 3 rise 2

Step 6: Automate Certificate Renewal

Let's Encrypt certificates expire every 90 days. Set up a renewal hook to automatically rebuild the combined PEM file and reload HAProxy:

# Create the renewal hook script
cat > /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh << 'EOF'
#!/bin/bash
# Run after Certbot successfully renews a certificate

DOMAIN="example.com"
CERT_DIR="/etc/letsencrypt/live/$DOMAIN"
HAPROXY_CERT="/etc/haproxy/certs/$DOMAIN.pem"

# Combine fullchain + private key into single PEM
cat "$CERT_DIR/fullchain.pem" "$CERT_DIR/privkey.pem" > "$HAPROXY_CERT"
chmod 600 "$HAPROXY_CERT"

# Gracefully reload HAProxy (zero downtime)
systemctl reload haproxy

echo "HAProxy SSL certificate renewed: $(date)"
EOF

chmod +x /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh

Certbot already runs daily via a systemd timer. Test your renewal setup:

# Test renewal in dry-run mode (won't actually renew)
certbot renew --dry-run

# Test the full renewal flow
certbot renew --force-renewal --deploy-hook /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh

Step 7: Enable HTTP/2

HTTP/2 is enabled by adding alpn h2,http/1.1 to your bind directive (already included in the config above). Verify it's working:

# Check if HTTP/2 is active
curl -I --http2 https://example.com
# Should see: HTTP/2 200

Step 8: Multiple Domains

HAProxy automatically serves the correct certificate for each domain when you put all PEM files in the same directory (used with crt /etc/haproxy/certs/):

# Get certs for a second domain
certbot certonly --standalone -d api.example.com --email you@example.com --agree-tos

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

# Reload HAProxy
systemctl reload haproxy

# Now /etc/haproxy/certs/ contains:
# example.com.pem
# api.example.com.pem
# HAProxy serves the right cert via SNI automatically

Troubleshooting

Problem: SSL certificate not found

Solution: Ensure the .pem file contains both the full certificate chain AND the private key. Check with: openssl x509 -in /etc/haproxy/certs/example.com.pem -text -noout

Problem: Certbot renewal fails: port 80 in use

Solution: Use the 'webroot' plugin instead of standalone, or configure the ACME challenge frontend in HAProxy to proxy to a local certbot webroot. The config above handles this.

Problem: Browser shows 'not secure' after setup

Solution: Check that the fullchain.pem (not just cert.pem) is in your combined file. Run: openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt /etc/haproxy/certs/example.com.pem

Problem: HAProxy reload fails after renewal

Solution: Check config syntax first: haproxy -c -f /etc/haproxy/haproxy.cfg. Then check permissions: the haproxy user must be able to read the .pem file.

SSL Configuration Checklist

  • TLS 1.2 minimum enforced (TLS 1.3 preferred)
  • SSLv3 and TLS 1.0/1.1 disabled
  • ECDHE cipher suites only (forward secrecy)
  • HTTP/2 enabled via ALPN
  • HSTS header with preload enabled
  • OCSP stapling enabled
  • Auto-renewal configured and tested
  • Combined PEM file secured (chmod 600)

For a complete security hardening guide beyond SSL, see: HAProxy Security Hardening: DDoS Protection, SSL & Rate Limiting

Need SSL configured correctly, not just running?

Our team sets up production-grade SSL with proper cipher suites, auto-renewal, HSTS preloading, and monitoring as part of our DevOps & Cloud service.

Talk to Our Team

SSL Setup Done. What's Next?

PentaSynth handles SSL termination, load balancing, DDoS protection, and full infrastructure monitoring for production systems. Let our DevOps team set up your complete stack.

See Our DevOps Services