Set up HAProxy with SSL termination, keepalived for high availability, and comprehensive health checks to distribute traffic across multiple backend servers with automatic failover.
Prerequisites
- Root or sudo access
- Multiple backend servers for load balancing
- Domain name for SSL certificate
- Basic understanding of networking concepts
What this solves
HAProxy provides enterprise-grade load balancing and high availability for web applications by distributing incoming requests across multiple backend servers. This tutorial covers setting up HAProxy with SSL termination, health checks, and keepalived for automatic failover, ensuring your services remain available even when individual servers fail.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest HAProxy version with security patches.
sudo apt update && sudo apt upgrade -yInstall HAProxy and keepalived
Install HAProxy for load balancing and keepalived for high availability clustering. These packages work together to provide automatic failover.
sudo apt install -y haproxy keepalived certbotCreate HAProxy directories and backup original config
Set up the directory structure for SSL certificates and create a backup of the default configuration file.
sudo mkdir -p /etc/haproxy/certs /var/lib/haproxy/stats
sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.backup
sudo chown -R haproxy:haproxy /var/lib/haproxyConfigure HAProxy main configuration
Create the main HAProxy configuration with global settings, SSL termination, and backend server definitions.
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
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# SSL Security
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
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
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/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"
# Default backend
default_backend web_servers
Backend server pool
backend web_servers
balance roundrobin
option httpchk GET /health
# Backend servers with health checks
server web1 203.0.113.10:80 check inter 5000 fall 3 rise 2
server web2 203.0.113.11:80 check inter 5000 fall 3 rise 2
server web3 203.0.113.12:80 check inter 5000 fall 3 rise 2Generate SSL certificate with Let's Encrypt
Create an SSL certificate for your domain and prepare it for HAProxy's SSL termination.
sudo certbot certonly --standalone -d example.com -d www.example.com
sudo cat /etc/letsencrypt/live/example.com/fullchain.pem /etc/letsencrypt/live/example.com/privkey.pem > /tmp/example.com.pem
sudo mv /tmp/example.com.pem /etc/haproxy/certs/
sudo chmod 600 /etc/haproxy/certs/example.com.pem
sudo chown haproxy:haproxy /etc/haproxy/certs/example.com.pemConfigure keepalived for high availability
Set up keepalived on the primary load balancer node to manage virtual IP failover.
vrrp_script chk_haproxy {
script "/bin/kill -0 cat /var/run/haproxy.pid"
interval 2
weight 2
fall 3
rise 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 101
advert_int 1
authentication {
auth_type PASS
auth_pass changeme123
}
virtual_ipaddress {
203.0.113.100/24
}
track_script {
chk_haproxy
}
}Configure rsyslog for HAProxy logging
Enable UDP logging in rsyslog to capture HAProxy access logs and errors.
$ModLoad imudp
$UDPServerRun 514
$UDPServerAddress 127.0.0.1
HAProxy log files
local0.* /var/log/haproxy.log
& stopEnable and start services
Start HAProxy and keepalived services and enable them to start automatically on boot.
sudo systemctl restart rsyslog
sudo systemctl enable --now haproxy
sudo systemctl enable --now keepalived
sudo systemctl status haproxy keepalivedConfigure 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 allow 112/tcp
sudo ufw reloadAdvanced configuration
Configure different load balancing algorithms
HAProxy supports multiple load balancing methods. Here's how to configure different algorithms for specific use cases.
# Least connections for long-running requests
backend api_servers
balance leastconn
option httpchk GET /api/health
server api1 203.0.113.20:8080 check
server api2 203.0.113.21:8080 check
Source IP hash for session persistence
backend app_servers
balance source
option httpchk GET /status
server app1 203.0.113.30:3000 check
server app2 203.0.113.31:3000 check
URI hash for cache optimization
backend cache_servers
balance uri
option httpchk GET /ping
server cache1 203.0.113.40:6379 check
server cache2 203.0.113.41:6379 checkSetup SSL certificate auto-renewal
Create a script to automatically renew Let's Encrypt certificates and reload HAProxy.
#!/bin/bash
DOMAIN="example.com"
cat /etc/letsencrypt/live/$DOMAIN/fullchain.pem /etc/letsencrypt/live/$DOMAIN/privkey.pem > /etc/haproxy/certs/$DOMAIN.pem
chmod 600 /etc/haproxy/certs/$DOMAIN.pem
chown haproxy:haproxy /etc/haproxy/certs/$DOMAIN.pem
systemctl reload haproxysudo chmod +x /etc/letsencrypt/renewal-hooks/post/haproxy-reload.shConfigure health check monitoring
Set up comprehensive health checks with custom endpoints and failure thresholds.
backend web_servers_enhanced
balance roundrobin
# Custom health check with headers
option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
http-check expect status 200
# Advanced server configuration
server web1 203.0.113.10:80 check inter 3000 fall 2 rise 3 maxconn 100 weight 100
server web2 203.0.113.11:80 check inter 3000 fall 2 rise 3 maxconn 100 weight 100
server web3 203.0.113.12:80 check inter 3000 fall 2 rise 3 maxconn 50 weight 50 backupPerformance tuning and security
Optimize HAProxy performance
Configure system-level optimizations for high-traffic environments.
haproxy soft nofile 65536
haproxy hard nofile 65536net.core.somaxconn = 65536
net.ipv4.tcp_max_syn_backlog = 65536
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 120sudo sysctl -p /etc/sysctl.d/99-haproxy.confConfigure rate limiting and DDoS protection
Add rate limiting to protect backend servers from abuse and DDoS attacks.
# Add to frontend https_frontend section
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
# Rate limiting - 20 requests per 10 seconds per IP
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 }
# Block malicious patterns
http-request deny if { path_beg -i /admin } && !{ src 203.0.113.0/24 }
http-request deny if { hdr_sub(user-agent) -i bot scanner }
default_backend web_serversConfigure secondary HAProxy node
Setup backup load balancer
Configure a second HAProxy node for true high availability with automatic failover.
# Configuration for secondary node
vrrp_script chk_haproxy {
script "/bin/kill -0 cat /var/run/haproxy.pid"
interval 2
weight 2
fall 3
rise 2
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass changeme123
}
virtual_ipaddress {
203.0.113.100/24
}
track_script {
chk_haproxy
}
}Verify your setup
sudo systemctl status haproxy keepalived
haproxy -f /etc/haproxy/haproxy.cfg -c
sudo ss -tlnp | grep :443
curl -I https://example.com
ip addr show | grep 203.0.113.100Access the HAProxy statistics page at http://your-server-ip:8404/stats to monitor backend server health and traffic distribution. You can also integrate this with Grafana and Prometheus for comprehensive monitoring.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| 503 Service Unavailable | All backend servers are down | Check backend server health: curl -I http://backend-ip/health |
| SSL certificate errors | Wrong certificate format | Ensure PEM file contains both cert and key: openssl x509 -in cert.pem -text -noout |
| Keepalived not failing over | VRRP traffic blocked | Allow VRRP protocol: sudo ufw allow 112/tcp |
| High memory usage | Too many concurrent connections | Tune maxconn in global and backend sections |
| Backend servers marked DOWN | Health check endpoint missing | Create /health endpoint on backend servers |
| Statistics page not accessible | Firewall blocking port 8404 | Allow stats port: sudo ufw allow 8404/tcp |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
DOMAIN="${1:-example.com}"
BACKEND_IPS="${2:-192.168.1.10,192.168.1.11,192.168.1.12}"
VIP="${3:-192.168.1.100}"
usage() {
echo -e "${BLUE}Usage: $0 [domain] [backend_ips] [virtual_ip]${NC}"
echo -e "${BLUE}Example: $0 example.com 192.168.1.10,192.168.1.11 192.168.1.100${NC}"
exit 1
}
log() {
echo -e "${GREEN}[INFO]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
cleanup() {
if [ $? -ne 0 ]; then
error "Installation failed. Performing cleanup..."
systemctl stop haproxy keepalived 2>/dev/null || true
[ -f /etc/haproxy/haproxy.cfg.backup ] && mv /etc/haproxy/haproxy.cfg.backup /etc/haproxy/haproxy.cfg
fi
}
trap cleanup ERR
# Check prerequisites
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root"
exit 1
fi
if [[ "$#" -eq 1 && ("$1" == "-h" || "$1" == "--help") ]]; then
usage
fi
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
FIREWALL_CMD="firewall-cmd"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
FIREWALL_CMD="firewall-cmd"
;;
*)
error "Unsupported distribution: $ID"
exit 1
;;
esac
else
error "Cannot detect distribution"
exit 1
fi
log "Installing HAProxy and Keepalived on $PRETTY_NAME"
# Step 1: Update system packages
echo -e "${BLUE}[1/8]${NC} Updating system packages..."
$PKG_UPDATE
# Step 2: Install required packages
echo -e "${BLUE}[2/8]${NC} Installing HAProxy, Keepalived, and Certbot..."
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL haproxy keepalived certbot rsyslog
else
$PKG_INSTALL haproxy keepalived certbot rsyslog
fi
# Step 3: Create directories and backup
echo -e "${BLUE}[3/8]${NC} Setting up directories and backing up configs..."
mkdir -p /etc/haproxy/certs /var/lib/haproxy/stats /run/haproxy
cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.backup
chown -R haproxy:haproxy /var/lib/haproxy /run/haproxy
chmod 755 /etc/haproxy/certs /var/lib/haproxy/stats /run/haproxy
# Step 4: Configure rsyslog for HAProxy
echo -e "${BLUE}[4/8]${NC} Configuring logging..."
cat > /etc/rsyslog.d/49-haproxy.conf << 'EOF'
$ModLoad imudp
$UDPServerRun 514
$UDPServerAddress 127.0.0.1
local0.* /var/log/haproxy.log
& stop
EOF
systemctl restart rsyslog
# Step 5: Configure HAProxy
echo -e "${BLUE}[5/8]${NC} Configuring HAProxy..."
cat > /etc/haproxy/haproxy.cfg << EOF
global
log 127.0.0.1:514 local0
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
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
frontend stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
stats admin if TRUE
frontend http_frontend
bind *:80
redirect scheme https code 301 if !{ ssl_fc }
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/${DOMAIN}.pem
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"
default_backend web_servers
backend web_servers
balance roundrobin
option httpchk GET /
EOF
# Add backend servers
IFS=',' read -ra BACKENDS <<< "$BACKEND_IPS"
for i in "${!BACKENDS[@]}"; do
echo " server web$((i+1)) ${BACKENDS[i]}:80 check inter 5000 fall 3 rise 2" >> /etc/haproxy/haproxy.cfg
done
# Step 6: Generate self-signed certificate (placeholder)
echo -e "${BLUE}[6/8]${NC} Creating temporary SSL certificate..."
openssl req -x509 -newkey rsa:2048 -keyout /tmp/${DOMAIN}.key -out /tmp/${DOMAIN}.crt -days 365 -nodes -subj "/CN=${DOMAIN}"
cat /tmp/${DOMAIN}.crt /tmp/${DOMAIN}.key > /etc/haproxy/certs/${DOMAIN}.pem
chmod 600 /etc/haproxy/certs/${DOMAIN}.pem
chown haproxy:haproxy /etc/haproxy/certs/${DOMAIN}.pem
rm /tmp/${DOMAIN}.key /tmp/${DOMAIN}.crt
# Step 7: Configure Keepalived
echo -e "${BLUE}[7/8]${NC} Configuring Keepalived..."
INTERFACE=$(ip route get 8.8.8.8 | awk 'NR==1 {print $(NF-2)}')
cat > /etc/keepalived/keepalived.conf << EOF
vrrp_script chk_haproxy {
script "/bin/curl -f http://localhost:8404/stats"
interval 2
weight 2
fall 3
rise 2
}
vrrp_instance VI_1 {
state MASTER
interface $INTERFACE
virtual_router_id 51
priority 110
advert_int 1
authentication {
auth_type PASS
auth_pass $(openssl rand -base64 8)
}
virtual_ipaddress {
$VIP
}
track_script {
chk_haproxy
}
}
EOF
# Configure firewall
if command -v ufw &> /dev/null; then
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 8404/tcp
elif command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --permanent --add-port=8404/tcp
firewall-cmd --reload
fi
# Enable and start services
systemctl enable haproxy keepalived
systemctl start haproxy keepalived
# Step 8: Verification
echo -e "${BLUE}[8/8]${NC} Verifying installation..."
sleep 3
if systemctl is-active --quiet haproxy; then
log "HAProxy is running"
else
error "HAProxy failed to start"
exit 1
fi
if systemctl is-active --quiet keepalived; then
log "Keepalived is running"
else
error "Keepalived failed to start"
exit 1
fi
if curl -s -o /dev/null -w "%{http_code}" http://localhost:8404/stats | grep -q "200"; then
log "HAProxy stats interface is accessible"
else
warn "HAProxy stats interface may not be accessible"
fi
log "Installation completed successfully!"
log "Access stats at: http://$VIP:8404/stats"
log "To get Let's Encrypt certificate, run:"
log "certbot certonly --standalone -d $DOMAIN"
log "Then combine cert: cat /etc/letsencrypt/live/$DOMAIN/fullchain.pem /etc/letsencrypt/live/$DOMAIN/privkey.pem > /etc/haproxy/certs/$DOMAIN.pem"
log "Don't forget to configure your backend servers at: ${BACKEND_IPS}"
Review the script before running. Execute with: bash install.sh