HAProxy Cheat Sheet
Overview
HAProxy (High Availability Proxy) is a free, open-source software written in C that provides extremely high performance load balancing and proxying for TCP and HTTP-based applications. It has become the de facto standard open-source load balancer, deployed in front of some of the world’s highest-traffic websites including GitHub, Twitter, and Reddit. HAProxy is single-threaded in design but can run multiple worker processes, and its event-driven architecture allows it to handle hundreds of thousands of concurrent connections with minimal memory overhead.
HAProxy operates across Layers 4 and 7 of the OSI model. In Layer 4 (TCP) mode, it routes connections based on IP addresses and ports without inspecting the content. In Layer 7 (HTTP) mode, it can route based on HTTP headers, cookies, paths, query parameters, and arbitrary content matching — making it possible to implement sophisticated routing logic, canary deployments, A/B testing, blue-green deployments, and circuit breaking.
The configuration model centers on four section types: global (process-level settings), defaults (inherited by all frontends and backends), frontend (defines listeners and initial routing), and backend (defines server pools and balancing). Access Control Lists (ACLs) and use_backend directives create conditional routing logic. HAProxy’s stick-table feature provides distributed session tracking for stateful applications. The built-in statistics page gives live visibility into connection rates, error rates, and server health.
Installation
Ubuntu/Debian
# From official repos (older version)
sudo apt update && sudo apt install -y haproxy
# Latest stable via PPA (recommended)
sudo add-apt-repository ppa:vbernat/haproxy-2.8
sudo apt update && sudo apt install -y haproxy=2.8.*
# Verify
haproxy -v
sudo systemctl status haproxy
RHEL/CentOS/Fedora
# Fedora
sudo dnf install -y haproxy
# RHEL 8/9 via EPEL
sudo dnf install -y epel-release
sudo dnf install -y haproxy
# Or install from SCL (Software Collections) for latest version
sudo dnf install -y centos-release-scl
sudo dnf install -y rh-haproxy20-haproxy
Build from Source (Latest)
# Install dependencies
sudo apt install -y build-essential libssl-dev libpcre3-dev zlib1g-dev \
libsystemd-dev liblua5.3-dev
# Download and build
curl -OL https://www.haproxy.org/download/2.8/src/haproxy-2.8.5.tar.gz
tar -xzf haproxy-2.8.5.tar.gz && cd haproxy-2.8.5
make -j$(nproc) TARGET=linux-glibc \
USE_OPENSSL=1 USE_PCRE=1 USE_ZLIB=1 \
USE_SYSTEMD=1 USE_LUA=1
sudo make install PREFIX=/usr/local
Docker
# Run HAProxy container
docker run -d \
--name haproxy \
-p 80:80 -p 443:443 -p 8404:8404 \
-v $PWD/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro \
--restart unless-stopped \
haproxy:2.8
# Reload config without downtime
docker kill -s HUP haproxy
Configuration
Minimal Working Config (/etc/haproxy/haproxy.cfg)
global
log /dev/log local0
log /dev/log local1 notice
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
maxconn 100000
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
tune.ssl.default-dh-param 2048
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5s
timeout client 50s
timeout server 50s
errorfile 400 /etc/haproxy/errors/400.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
frontend http_front
bind *:80
default_backend web_servers
# Redirect to HTTPS
redirect scheme https code 301 if !{ ssl_fc }
frontend https_front
bind *:443 ssl crt /etc/haproxy/certs/
default_backend web_servers
backend web_servers
balance roundrobin
option httpchk GET /health
server web1 192.168.1.10:8080 check
server web2 192.168.1.11:8080 check
server web3 192.168.1.12:8080 check backup
Core Commands
| Command | Description |
|---|---|
haproxy -f haproxy.cfg -c | Validate configuration file |
haproxy -f haproxy.cfg | Start HAProxy with config |
haproxy -sf $(cat /var/run/haproxy.pid) | Graceful reload (keeps connections) |
haproxy -st $(cat /var/run/haproxy.pid) | Hard reload (terminates connections) |
sudo systemctl reload haproxy | Graceful reload via systemd |
sudo systemctl restart haproxy | Full restart via systemd |
echo "show info" | socat stdio /run/haproxy/admin.sock | Show runtime info |
echo "show stat" | socat stdio /run/haproxy/admin.sock | Show stats (CSV) |
echo "show servers state" | socat stdio /run/haproxy/admin.sock | Show server states |
echo "disable server backend/server1" | socat stdio /run/haproxy/admin.sock | Disable a backend server |
echo "enable server backend/server1" | socat stdio /run/haproxy/admin.sock | Enable a backend server |
echo "set weight backend/server1 50" | socat stdio /run/haproxy/admin.sock | Set server weight |
echo "show table" | socat stdio /run/haproxy/admin.sock | Show stick tables |
echo "clear counters all" | socat stdio /run/haproxy/admin.sock | Reset all counters |
hatop -s /run/haproxy/admin.sock | Interactive terminal dashboard (requires hatop) |
Advanced Usage
ACL-Based Routing
frontend http_front
bind *:80
# Define ACLs
acl is_api path_beg /api/
acl is_static path_end .css .js .png .jpg .gif .ico .woff .woff2
acl is_websocket hdr(Upgrade) -i websocket
acl is_mobile hdr(User-Agent) -i -m sub mobile android iphone
acl host_admin hdr(Host) -i admin.example.com
acl src_internal src 10.0.0.0/8 192.168.0.0/16
# Apply routing based on ACLs
use_backend api_servers if is_api
use_backend static_servers if is_static
use_backend ws_servers if is_websocket
use_backend admin_backend if host_admin src_internal
default_backend web_servers
backend api_servers
balance leastconn
option httpchk GET /api/health
http-check expect status 200
server api1 192.168.1.20:8000 check
server api2 192.168.1.21:8000 check
backend static_servers
balance roundrobin
server cdn1 192.168.1.30:80 check
server cdn2 192.168.1.31:80 check
backend ws_servers
balance leastconn
timeout tunnel 1h
option http-server-close
server ws1 192.168.1.40:8080 check
Stick Tables for Session Persistence
backend web_servers
balance roundrobin
# Cookie-based persistence
cookie SERVERID insert indirect nocache
server web1 192.168.1.10:8080 check cookie s1
server web2 192.168.1.11:8080 check cookie s2
# IP-hash persistence without cookies
backend sticky_servers
balance source
hash-type consistent
server web1 192.168.1.10:8080 check
server web2 192.168.1.11:8080 check
# Stick table for rate limiting
frontend http_front
bind *:80
stick-table type ip size 1m expire 10m store http_req_rate(10s),conn_cur,gpc0
http-request track-sc0 src
# Block if > 100 requests in 10s
http-request deny if { sc_http_req_rate(0) gt 100 }
# Block if > 50 concurrent connections
http-request deny if { sc_conn_cur(0) gt 50 }
default_backend web_servers
Health Checks
backend app_servers
# HTTP health check
option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
http-check expect status 200-399
# Custom check interval
default-server inter 3s fall 3 rise 2 slowstart 30s
# TCP check only
# option tcp-check
# External check script
# external-check path "/etc/haproxy/checks"
# external-check command /etc/haproxy/checks/mycheck.sh
server app1 192.168.1.10:8080 check
server app2 192.168.1.11:8080 check
server app3 192.168.1.12:8080 check weight 50
server backup 192.168.1.13:8080 check backup
Rate Limiting and WAF-like Rules
frontend http_front
bind *:80
# Stick table for rate tracking
stick-table type ip size 200k expire 30s store http_req_rate(30s),http_err_rate(30s),conn_rate(3s)
http-request track-sc0 src
# Rate limiting rules
# Block bots hitting 404 too frequently
acl too_many_errors sc_http_err_rate(0) gt 20
# Block high connection rate
acl too_many_connections sc_conn_rate(0) gt 100
# Block too many requests per 30s
acl too_many_requests sc_http_req_rate(0) gt 300
http-request deny deny_status 429 if too_many_errors
http-request deny deny_status 429 if too_many_connections
http-request deny deny_status 429 if too_many_requests
# Block bad User-Agents
acl bad_ua hdr_sub(user-agent) -i curl wget python-requests nikto sqlmap
http-request deny if bad_ua
# Block direct IP access (require Host header)
acl has_host hdr(host) -m found
http-request deny if !has_host
# Require specific header for API
acl is_api path_beg /api/
acl has_api_key hdr(X-API-Key) -m found
http-request deny if is_api !has_api_key
default_backend web_servers
TLS Termination and Certificate Management
global
ssl-default-bind-ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:!aNULL:!MD5:!DSS
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
tune.ssl.default-dh-param 4096
frontend https_front
bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
# HSTS header
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# SNI-based routing
use_backend api_backend if { ssl_fc_sni api.example.com }
use_backend admin_backend if { ssl_fc_sni admin.example.com }
default_backend web_servers
# HTTP to HTTPS redirect
frontend http_redirect
bind *:80
redirect scheme https code 301
# Certificate bundle: cat cert.pem privkey.pem > /etc/haproxy/certs/example.com.pem
Statistics Dashboard
frontend stats
bind *:8404
# Restrict to internal IPs
acl internal src 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16
http-request deny if !internal
stats enable
stats uri /stats
stats refresh 10s
stats show-legends
stats show-node
stats auth admin:strongpassword
stats admin if TRUE
# Access stats page
curl -u admin:strongpassword http://localhost:8404/stats
# Or via browser at http://localhost:8404/stats
Lua Scripting
global
lua-load /etc/haproxy/lua/rate_limit.lua
frontend http_front
bind *:80
http-request lua.rate_check
default_backend web_servers
-- /etc/haproxy/lua/rate_check.lua
core.register_action("rate_check", { "http-req" }, function(txn)
local src = txn.f:src()
-- Custom Lua logic here
txn:set_var("req.rate_ok", true)
end)
Common Workflows
Blue-Green Deployment
backend blue_backend
server blue1 192.168.1.10:8080 check
server blue2 192.168.1.11:8080 check
backend green_backend
server green1 192.168.1.20:8080 check
server green2 192.168.1.21:8080 check
frontend http_front
bind *:80
# Switch traffic via environment variable or ACL
# Use HAProxy runtime API to switch:
# echo "set map /etc/haproxy/maps/active_env.map / green" | socat ...
use_backend green_backend if { var(txn.env) -m str green }
default_backend blue_backend
# Switch to green deployment
echo "set map /etc/haproxy/active.map / green_backend" \
| socat stdio /run/haproxy/admin.sock
# Drain blue servers
echo "set server blue_backend/blue1 state drain" \
| socat stdio /run/haproxy/admin.sock
Canary Deployment (Traffic Splitting)
backend canary_backend
balance roundrobin
server canary 192.168.1.99:8080 check weight 10
backend stable_backend
balance roundrobin
server stable1 192.168.1.10:8080 check weight 90
server stable2 192.168.1.11:8080 check weight 90
frontend http_front
bind *:80
# 10% traffic to canary, 90% to stable
use_backend canary_backend if { rand(10) lt 1 }
default_backend stable_backend
Draining a Server for Maintenance
# Gracefully drain connections from a server
echo "set server web_servers/web1 state drain" \
| socat stdio /run/haproxy/admin.sock
# Wait for connections to drain, then disable
echo "set server web_servers/web1 state maint" \
| socat stdio /run/haproxy/admin.sock
# Re-enable after maintenance
echo "set server web_servers/web1 state ready" \
| socat stdio /run/haproxy/admin.sock
Monitoring and Alerting
# Parse stats via CLI
echo "show stat" | socat stdio /run/haproxy/admin.sock | \
awk -F, '{print $1,$2,$18,$19,$20}' | column -t
# Check specific backend status
echo "show servers state web_servers" | socat stdio /run/haproxy/admin.sock
# Export Prometheus metrics (requires haproxy_exporter or Prometheus module)
# Built-in since HAProxy 2.0:
# Listen on :8405/metrics for Prometheus format
# Log analysis
grep 'web1' /var/log/haproxy.log | awk '{print $NF}' | sort | uniq -c
Tips and Best Practices
| Practice | Details |
|---|---|
| Always validate config | Run haproxy -c -f haproxy.cfg before every reload |
Use systemctl reload | Prefer reload over restart to avoid connection drops |
Set maxconn appropriately | Default is often too low; tune based on available memory and file descriptors |
| Enable stats socket | stats socket /run/haproxy/admin.sock is essential for runtime management |
option forwardfor | Always add to pass real client IP to backends via X-Forwarded-For header |
option http-server-close | Prefer over option forceclose; allows connection reuse to clients |
| Health check tuning | Set inter, fall, rise in default-server for consistent health behavior |
slowstart for warm-up | Use slowstart 60s on backend servers to ramp traffic after restart |
| Stick table size | Size stick tables with size directive based on expected unique clients |
| Separate SSL termination | Consider offloading TLS to HAProxy and using HTTP internally for simpler certs |
tcp-check for non-HTTP | Use TCP health checks for databases, message queues, and custom protocols |
| Log format customization | Define log-format in defaults section to include response time and backend name |