Configure HAProxy multi-site SSL termination with SNI for secure load balancing

Intermediate 45 min Apr 17, 2026 23 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up HAProxy to handle SSL certificates for multiple domains using Server Name Indication (SNI), enabling secure HTTPS traffic termination and load balancing across different backend services.

Prerequisites

  • Root or sudo access
  • Multiple SSL certificates for different domains
  • Backend web servers running on HTTP
  • Basic understanding of SSL/TLS certificates

What this solves

This tutorial shows you how to configure HAProxy to terminate SSL/HTTPS connections for multiple websites using Server Name Indication (SNI). You'll set up certificate management, secure cipher suites, and backend health checks for production-grade load balancing. This is essential when you need to serve HTTPS traffic for multiple domains through a single load balancer.

Step-by-step configuration

Install HAProxy

Install HAProxy with SSL support from your distribution's package manager.

sudo apt update
sudo apt install -y haproxy ssl-cert
sudo dnf update -y
sudo dnf install -y haproxy openssl

Create SSL certificate directory

Create a secure directory structure for storing SSL certificates that HAProxy can access.

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

Prepare SSL certificates

HAProxy requires certificates in PEM format with the private key and certificate chain concatenated. Create certificate files for each domain.

# For each domain, combine cert and key into single PEM file
sudo cat /path/to/example.com.crt /path/to/example.com.key > /etc/ssl/haproxy/example.com.pem
sudo cat /path/to/api.example.com.crt /path/to/api.example.com.key > /etc/ssl/haproxy/api.example.com.pem
sudo cat /path/to/app.example.com.crt /path/to/app.example.com.key > /etc/ssl/haproxy/app.example.com.pem
Note: If using Let's Encrypt, certificates are typically in /etc/letsencrypt/live/domain/. The fullchain.pem contains the certificate chain, and privkey.pem contains the private key.

Set certificate permissions

Secure the certificate files with appropriate permissions for HAProxy access.

sudo chmod 640 /etc/ssl/haproxy/*.pem
sudo chown root:haproxy /etc/ssl/haproxy/*.pem
Never use chmod 777. SSL private keys must be protected. HAProxy runs as the haproxy user and only needs read access to certificate files.

Backup original configuration

Create a backup of the default HAProxy configuration before making changes.

sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.backup

Configure HAProxy global settings

Configure the global section with SSL security settings and performance tuning.

global
    daemon
    user haproxy
    group haproxy
    pidfile /var/run/haproxy.pid
    
    # SSL Configuration
    ssl-default-bind-ciphers ECDHE+aRSA+AES256+GCM+SHA384:ECDHE+aRSA+AES128+GCM+SHA256:ECDHE+aRSA+AES256+SHA384:ECDHE+aRSA+AES128+SHA256
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
    ssl-default-server-ciphers ECDHE+aRSA+AES256+GCM+SHA384:ECDHE+aRSA+AES128+GCM+SHA256:ECDHE+aRSA+AES256+SHA384:ECDHE+aRSA+AES128+SHA256
    ssl-default-server-options ssl-min-ver TLSv1.2 no-tls-tickets
    
    # Performance tuning
    tune.ssl.default-dh-param 2048
    tune.bufsize 32768
    
    # Logging
    log 127.0.0.1:514 local0
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms
    option httplog
    option dontlognull
    option http-server-close
    option forwardfor
    option redispatch
    retries 3
    
    # Security headers
    http-response set-header X-Frame-Options SAMEORIGIN
    http-response set-header X-Content-Type-Options nosniff
    http-response set-header X-XSS-Protection "1; mode=block"
    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"

Configure HTTPS frontend with SNI

Set up the frontend to handle HTTPS traffic and route requests based on the SNI hostname.

# HTTPS Frontend with SNI
frontend https_frontend
    bind *:443 ssl crt /etc/ssl/haproxy/
    mode http
    
    # Redirect HTTP to HTTPS (optional)
    redirect scheme https if !{ ssl_fc }
    
    # SNI-based routing
    use_backend example_backend if { ssl_fc_sni example.com }
    use_backend api_backend if { ssl_fc_sni api.example.com }
    use_backend app_backend if { ssl_fc_sni app.example.com }
    
    # Default backend for unmatched domains
    default_backend default_backend

HTTP Frontend (redirect to HTTPS)

frontend http_frontend bind *:80 mode http redirect scheme https code 301

Configure backend server pools

Define backend server pools with health checks and load balancing algorithms.

# Backend for example.com
backend example_backend
    mode http
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200
    
    server web1 192.168.1.10:80 check inter 5s fall 3 rise 2
    server web2 192.168.1.11:80 check inter 5s fall 3 rise 2
    server web3 192.168.1.12:80 check inter 5s fall 3 rise 2

Backend for api.example.com

backend api_backend mode http balance leastconn option httpchk GET /api/health http-check expect status 200 server api1 192.168.1.20:8080 check inter 10s fall 3 rise 2 server api2 192.168.1.21:8080 check inter 10s fall 3 rise 2

Backend for app.example.com

backend app_backend mode http balance source option httpchk GET /status http-check expect status 200 server app1 192.168.1.30:3000 check inter 5s fall 3 rise 2 server app2 192.168.1.31:3000 check inter 5s fall 3 rise 2 server app3 192.168.1.32:3000 check inter 5s fall 3 rise 2

Default backend for unmatched domains

backend default_backend mode http http-request deny

Configure HAProxy statistics

Enable the statistics interface for monitoring backend health and performance.

# Statistics interface
listen stats
    bind *:8404
    stats enable
    stats uri /stats
    stats refresh 30s
    stats admin if TRUE
    stats auth admin:your-secure-password-here

Configure rsyslog for HAProxy logging

Set up dedicated logging for HAProxy to track SSL termination and backend health.

# HAProxy logging configuration
$ModLoad imudp
$UDPServerRun 514
$UDPServerAddress 127.0.0.1

HAProxy log files

local0.* /var/log/haproxy.log & stop

Restart rsyslog and test configuration

Apply the logging configuration and validate the HAProxy configuration syntax.

sudo systemctl restart rsyslog
sudo haproxy -c -f /etc/haproxy/haproxy.cfg

Enable and start HAProxy

Start HAProxy and enable it to start automatically on boot.

sudo systemctl enable haproxy
sudo systemctl start haproxy
sudo systemctl status haproxy

Configure firewall rules

Open the necessary ports for HTTPS, HTTP, and 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

Verify your setup

Test SSL termination, SNI routing, and backend health checks to ensure everything works correctly.

# Check HAProxy status and processes
sudo systemctl status haproxy
ps aux | grep haproxy

Test SSL certificate loading

sudo haproxy -c -f /etc/haproxy/haproxy.cfg

Test HTTPS connectivity for each domain

curl -I https://example.com curl -I https://api.example.com curl -I https://app.example.com

Check SSL certificate details

openssl s_client -connect example.com:443 -servername example.com < /dev/null

Verify backend server health

curl -s http://your-haproxy-server:8404/stats

Check HAProxy logs

sudo tail -f /var/log/haproxy.log

Configure advanced SSL security

Generate DH parameters

Generate stronger Diffie-Hellman parameters for enhanced SSL security.

sudo openssl dhparam -out /etc/ssl/haproxy/dhparams.pem 2048
sudo chmod 644 /etc/ssl/haproxy/dhparams.pem

Update HAProxy with DH parameters

Configure HAProxy to use the custom DH parameters for SSL connections.

# Add to global section
global
    # existing configuration...
    ssl-dh-param-file /etc/ssl/haproxy/dhparams.pem
    
    # Enhanced cipher suites
    ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-options prefer-client-ciphers ssl-min-ver TLSv1.2 no-tls-tickets

Implement OCSP stapling

Enable OCSP stapling for better SSL performance and security validation.

# Update certificate files with OCSP stapling
sudo mkdir -p /etc/ssl/haproxy/ocsp

For each certificate, fetch OCSP response (example for one domain)

sudo openssl x509 -in /etc/ssl/haproxy/example.com.pem -text -noout | grep -A4 "OCSP"

Update HAProxy configuration

sudo systemctl reload haproxy

Set up certificate automation

Create certificate renewal script

Automate certificate renewal and HAProxy reload for Let's Encrypt certificates.

#!/bin/bash

Certificate renewal script for HAProxy

set -e CERT_DIR="/etc/ssl/haproxy" LETSENCRYPT_DIR="/etc/letsencrypt/live"

Array of domains

DOMAINS=("example.com" "api.example.com" "app.example.com")

Function to combine cert and key

combine_cert() { local domain=$1 if [ -f "$LETSENCRYPT_DIR/$domain/fullchain.pem" ] && [ -f "$LETSENCRYPT_DIR/$domain/privkey.pem" ]; then cat "$LETSENCRYPT_DIR/$domain/fullchain.pem" "$LETSENCRYPT_DIR/$domain/privkey.pem" > "$CERT_DIR/$domain.pem" chmod 640 "$CERT_DIR/$domain.pem" chown root:haproxy "$CERT_DIR/$domain.pem" echo "Combined certificate for $domain" else echo "Certificate files not found for $domain" exit 1 fi }

Renew certificates

certbot renew --quiet

Combine certificates

for domain in "${DOMAINS[@]}"; do combine_cert "$domain" done

Test configuration and reload HAProxy

if haproxy -c -f /etc/haproxy/haproxy.cfg; then systemctl reload haproxy echo "HAProxy reloaded successfully" else echo "HAProxy configuration test failed" exit 1 fi

Make script executable and set up cron

Schedule automatic certificate renewal to run weekly.

sudo chmod +x /usr/local/bin/renew-haproxy-certs.sh

Add to root crontab

sudo crontab -e

Add this line to run weekly on Sundays at 2 AM

0 2 0 /usr/local/bin/renew-haproxy-certs.sh >> /var/log/cert-renewal.log 2>&1

Monitor and troubleshoot

Set up log rotation

Configure log rotation for HAProxy logs to prevent disk space issues.

/var/log/haproxy.log {
    daily
    rotate 30
    missingok
    notifempty
    compress
    delaycompress
    postrotate
        /bin/kill -HUP $(cat /var/run/rsyslogd.pid 2>/dev/null) 2>/dev/null || true
        /bin/kill -USR1 $(cat /var/run/haproxy.pid 2>/dev/null) 2>/dev/null || true
    endscript
}

Create monitoring script

Set up basic monitoring to check backend health and SSL certificate expiration.

#!/bin/bash

HAProxy monitoring script

set -e

Check HAProxy status

if ! systemctl is-active --quiet haproxy; then echo "CRITICAL: HAProxy is not running" exit 2 fi

Check SSL certificate expiration (30 days warning)

for cert in /etc/ssl/haproxy/*.pem; do if [ -f "$cert" ]; then domain=$(basename "$cert" .pem) expiry_date=$(openssl x509 -in "$cert" -noout -enddate | cut -d= -f2) expiry_seconds=$(date -d "$expiry_date" +%s) current_seconds=$(date +%s) days_until_expiry=$(( (expiry_seconds - current_seconds) / 86400 )) if [ $days_until_expiry -lt 30 ]; then echo "WARNING: Certificate for $domain expires in $days_until_expiry days" else echo "OK: Certificate for $domain expires in $days_until_expiry days" fi fi done

Check backend server health

curl -s http://localhost:8404/stats | grep -E "(UP|DOWN)" | while read line; do echo "Backend status: $line" done echo "HAProxy monitoring complete"

Make monitoring script executable

Set permissions and test the monitoring script.

sudo chmod +x /usr/local/bin/monitor-haproxy.sh
sudo /usr/local/bin/monitor-haproxy.sh

Common issues

Symptom Cause Fix
HAProxy fails to start with SSL error Certificate file format or permissions issue Check sudo haproxy -c -f /etc/haproxy/haproxy.cfg and verify certificate format
SNI routing not working Certificate not matching domain or missing in directory Verify certificate filename matches domain and check ssl_fc_sni conditions
Backend servers showing as DOWN Health check endpoint not responding or network issue Test health check URL manually and verify backend server connectivity
SSL handshake failures Cipher suite mismatch or TLS version incompatibility Review cipher configuration and check client TLS support
High CPU usage during SSL termination Insufficient DH parameters or cipher suite inefficiency Generate 2048-bit DH params and optimize cipher suites
Statistics page not accessible Firewall blocking port 8404 or authentication failure Check firewall rules and verify stats authentication credentials

Next steps

Running this in production?

Want this handled for you? Setting this up once is straightforward. Keeping it patched, monitored, backed up and tuned across environments is the harder part. See how we run infrastructure like this for European SaaS and e-commerce teams.

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.