Implement HAProxy SSL termination with Let's Encrypt certificates for secure load balancing

Intermediate 45 min Apr 08, 2026 1,263 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure HAProxy to handle SSL termination with automated Let's Encrypt certificates, enabling secure HTTPS load balancing across multiple backend servers. This setup reduces CPU load on backend servers while providing centralized SSL certificate management.

Prerequisites

  • Root or sudo access
  • Domain name pointing to server
  • Backend web servers configured
  • Basic knowledge of SSL/TLS concepts

What this solves

SSL termination at the load balancer level allows HAProxy to handle HTTPS connections and forward unencrypted traffic to backend servers. This reduces computational overhead on your application servers while centralizing certificate management. Let's Encrypt provides free SSL certificates with automated renewal, making this solution both secure and cost-effective for production environments.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest versions of all components.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install HAProxy and Certbot

Install HAProxy for load balancing and Certbot for Let's Encrypt certificate management.

sudo apt install -y haproxy certbot socat
sudo dnf install -y haproxy certbot socat epel-release

Configure firewall rules

Open the necessary ports for HTTP, HTTPS, and HAProxy statistics interface.

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 8404/tcp
sudo ufw reload
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --permanent --add-port=8404/tcp
sudo firewall-cmd --reload

Create SSL certificate directory

Create a dedicated directory for SSL certificates with proper permissions for HAProxy access.

sudo mkdir -p /etc/ssl/certs/haproxy
sudo chown root:haproxy /etc/ssl/certs/haproxy
sudo chmod 750 /etc/ssl/certs/haproxy

Obtain Let's Encrypt certificates

Use Certbot standalone mode to obtain SSL certificates for your domain. Replace example.com with your actual domain.

sudo certbot certonly --standalone --preferred-challenges http --http-01-port 80 -d example.com -d www.example.com
Note: Ensure your domain points to this server's IP address and that no other service is using port 80 during certificate generation.

Create combined certificate file

HAProxy requires the certificate and private key in a single file. Create this combined file from the Let's Encrypt certificates.

sudo cat /etc/letsencrypt/live/example.com/fullchain.pem /etc/letsencrypt/live/example.com/privkey.pem | sudo tee /etc/ssl/certs/haproxy/example.com.pem
sudo chmod 640 /etc/ssl/certs/haproxy/example.com.pem
sudo chown root:haproxy /etc/ssl/certs/haproxy/example.com.pem

Configure HAProxy SSL termination

Create the main HAProxy configuration with SSL termination, backend health checks, and security headers.

global
    log         127.0.0.1:514 local0
    chroot      /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user        haproxy
    group       haproxy
    daemon
    
    # SSL Configuration
    ssl-default-bind-ciphers ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!SHA1:!AESCCM
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
    ssl-default-server-ciphers ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!SHA1:!AESCCM
    ssl-default-server-options ssl-min-ver TLSv1.2 no-tls-tickets
    
    # Performance tuning
    tune.ssl.default-dh-param 2048
    tune.bufsize 32768

defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option                  http-server-close
    option                  forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000

# Statistics interface
frontend stats
    bind *:8404
    stats enable
    stats uri /stats
    stats refresh 30s
    stats admin if TRUE

# HTTP frontend - redirect to HTTPS
frontend http_frontend
    bind *:80
    redirect scheme https code 301 if !{ ssl_fc }

# HTTPS frontend with SSL termination
frontend https_frontend
    bind *:443 ssl crt /etc/ssl/certs/haproxy/example.com.pem
    
    # Security headers
    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    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"
    http-response set-header Referrer-Policy "strict-origin-when-cross-origin"
    
    # Rate limiting
    stick-table type ip size 100k expire 30s store http_req_rate(10s)
    http-request track-sc0 src
    http-request deny if { sc_http_req_rate(0) gt 20 }
    
    # Default backend
    default_backend web_servers

# Backend server configuration
backend web_servers
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200
    
    # Backend servers
    server web1 192.168.1.10:80 check inter 3000 rise 2 fall 3
    server web2 192.168.1.11:80 check inter 3000 rise 2 fall 3
    server web3 192.168.1.12:80 backup check inter 3000 rise 2 fall 3

Create certificate renewal script

Create an automated script to renew certificates and reload HAProxy configuration.

#!/bin/bash

DOMAIN="example.com"
CERT_PATH="/etc/letsencrypt/live/$DOMAIN"
HAPROXY_CERT_PATH="/etc/ssl/certs/haproxy/$DOMAIN.pem"
LOG_FILE="/var/log/haproxy-cert-renewal.log"

# Function to log messages
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

# Renew certificates
log_message "Starting certificate renewal for $DOMAIN"

# Stop HAProxy temporarily for standalone renewal
sudo systemctl stop haproxy

# Renew certificate
if sudo certbot renew --standalone --preferred-challenges http --http-01-port 80; then
    log_message "Certificate renewal successful"
    
    # Create combined certificate file
    if sudo cat "$CERT_PATH/fullchain.pem" "$CERT_PATH/privkey.pem" | sudo tee "$HAPROXY_CERT_PATH" > /dev/null; then
        sudo chmod 640 "$HAPROXY_CERT_PATH"
        sudo chown root:haproxy "$HAPROXY_CERT_PATH"
        log_message "Combined certificate file created successfully"
    else
        log_message "ERROR: Failed to create combined certificate file"
        exit 1
    fi
    
    # Start HAProxy
    if sudo systemctl start haproxy; then
        log_message "HAProxy restarted successfully"
    else
        log_message "ERROR: Failed to restart HAProxy"
        exit 1
    fi
    
    # Verify HAProxy is running
    if sudo systemctl is-active --quiet haproxy; then
        log_message "Certificate renewal process completed successfully"
    else
        log_message "ERROR: HAProxy is not running after restart"
        exit 1
    fi
else
    log_message "ERROR: Certificate renewal failed"
    sudo systemctl start haproxy
    exit 1
fi

Make renewal script executable

Set proper permissions for the certificate renewal script.

sudo chmod 750 /usr/local/bin/renew-haproxy-certs.sh
sudo chown root:haproxy /usr/local/bin/renew-haproxy-certs.sh

Configure automatic certificate renewal

Create a systemd timer for automated certificate renewal every 30 days.

[Unit]
Description=Renew HAProxy SSL certificates
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/renew-haproxy-certs.sh
User=root
Group=haproxy

Create systemd timer

Configure the timer to run certificate renewal monthly.

[Unit]
Description=Run HAProxy certificate renewal monthly
Requires=haproxy-cert-renewal.service

[Timer]
OnCalendar=monthly
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target

Enable and test the renewal system

Enable the systemd timer and test the renewal script.

sudo systemctl daemon-reload
sudo systemctl enable haproxy-cert-renewal.timer
sudo systemctl start haproxy-cert-renewal.timer
sudo systemctl status haproxy-cert-renewal.timer

Validate and start HAProxy

Check the configuration syntax and start HAProxy with SSL termination.

sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl enable --now haproxy
sudo systemctl status haproxy

Configure SSL security hardening

Add additional security configurations for enhanced SSL protection.

# SSL Security Parameters
# Include this in your main haproxy.cfg global section

# DH Parameters
ssl-dh-param-file /etc/ssl/certs/dhparam.pem

# SSL Session Cache
tune.ssl.cachesize 100000
tune.ssl.lifetime 300
tune.ssl.maxrecord 1460

# Security Headers Template
http-response set-header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self'; object-src 'none'; media-src 'self'; frame-src 'none';"
http-response set-header Permissions-Policy "geolocation=(), microphone=(), camera=()"
http-response set-header X-Permitted-Cross-Domain-Policies "none"

Generate DH parameters

Create strong Diffie-Hellman parameters for enhanced SSL security.

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
sudo chmod 644 /etc/ssl/certs/dhparam.pem
Note: This process may take several minutes to complete as it generates cryptographically secure parameters.

Verify your setup

Test SSL termination, certificate validity, and load balancer functionality.

# Check HAProxy status and logs
sudo systemctl status haproxy
sudo journalctl -u haproxy -f --no-pager

# Test SSL certificate
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates

# Verify SSL configuration
curl -I https://example.com

# Check HAProxy statistics
curl http://localhost:8404/stats

# Test backend connectivity
sudo haproxy -c -f /etc/haproxy/haproxy.cfg

# Monitor certificate expiration
sudo certbot certificates

Performance optimization

Configure connection tuning

Optimize HAProxy for high-traffic environments with advanced connection settings.

# Add to global section for performance optimization
nbproc 1
nbthread 4
cpu-map auto:1/1-4 0-3

# Advanced connection settings
tune.maxaccept 100
tune.http.maxhdr 100
tune.ssl.default-dh-param 2048

# Memory optimization
tune.bufsize 32768
tune.maxrewrite 8192
global.maxconn 10000

Implement health check monitoring

Configure comprehensive health checks for backend servers with custom endpoints.

# Enhanced backend configuration
backend web_servers
    balance roundrobin
    option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
    http-check expect string "OK"
    
    # Advanced health check settings
    option tcp-check
    option log-health-checks
    
    # Server configurations with detailed health checks
    server web1 192.168.1.10:80 check inter 5000 rise 3 fall 2 maxconn 500 weight 100
    server web2 192.168.1.11:80 check inter 5000 rise 3 fall 2 maxconn 500 weight 100
    server web3 192.168.1.12:80 check inter 5000 rise 3 fall 2 maxconn 250 weight 50 backup

Common issues

SymptomCauseFix
SSL handshake failuresCombined certificate file missing or incorrect permissionsRecreate combined cert: sudo cat /etc/letsencrypt/live/example.com/{fullchain,privkey}.pem > /etc/ssl/certs/haproxy/example.com.pem
503 Service UnavailableBackend servers unreachable or health checks failingCheck backend server status and health check endpoint: curl http://backend-ip/health
Certificate renewal failsPort 80 blocked or HAProxy not stopped during renewalEnsure firewall allows port 80 and script stops HAProxy: sudo systemctl stop haproxy && sudo certbot renew
HAProxy won't startConfiguration syntax errorsValidate configuration: sudo haproxy -c -f /etc/haproxy/haproxy.cfg
Stats page not accessibleFirewall blocking port 8404Open stats port: sudo ufw allow 8404/tcp or sudo firewall-cmd --add-port=8404/tcp
Mixed content warningsBackend serving HTTP resources over HTTPSConfigure backend to serve HTTPS or update application URLs to relative paths

Next steps

Automated install script

Run this to automate the entire setup

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. Fully managed, with one fixed contact who knows your setup.

You get one fixed contact who knows your setup

Rotterdam 00:29 · reachable in a message, no ticket form