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

Intermediate 45 min Apr 08, 2026 103 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

Need help?

Don't want to manage this yourself?

We handle private cloud infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.