HAProxy Configuration Guide: TCP/HTTP Load Balancing, Health Checks, and SSL
HAProxy's Configuration Model
HAProxy's config is split into four sections:
global— process-level settings (logging, limits, socket)defaults— defaults inherited by all proxiesfrontend— listens for incoming connections, routes to backendsbackend— pool of servers + health check configlisten— combines frontend + backend in one block (used for simple TCP)
Understanding this model makes the config readable and predictable.
Installation
# RHEL/Rocky/AlmaLinux
sudo dnf install haproxy -y
# Ubuntu/Debian
sudo apt install haproxy -y
# Check version
haproxy -v
sudo systemctl enable haproxy
Base Configuration
/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
stats timeout 30s
user haproxy
group haproxy
daemon
maxconn 100000
# TLS tuning
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
log global
mode http
option httplog
option dontlognull
option forwardfor
option http-server-close
timeout connect 5s
timeout client 50s
timeout server 50s
timeout http-request 10s
timeout http-keep-alive 2s
timeout queue 30s
retries 3
errorfile 400 /etc/haproxy/errors/400.http
errorfile 503 /etc/haproxy/errors/503.http
Frontend Configuration
frontend http_in
bind *:80
# Redirect all HTTP to HTTPS
redirect scheme https code 301
frontend https_in
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1
# Security headers
http-response set-header Strict-Transport-Security "max-age=63072000"
http-response set-header X-Content-Type-Options "nosniff"
http-response set-header X-Frame-Options "SAMEORIGIN"
# Route by hostname
use_backend api_servers if { hdr(host) -i api.example.com }
use_backend static_cdn if { path_beg /static/ /assets/ /images/ }
default_backend web_servers
Backend Configuration
backend web_servers
balance roundrobin
option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
server web01 10.0.1.10:8080 check inter 5s fall 3 rise 2 weight 1
server web02 10.0.1.11:8080 check inter 5s fall 3 rise 2 weight 1
server web03 10.0.1.12:8080 check inter 5s fall 3 rise 2 weight 2
server web04 10.0.1.13:8080 check inter 5s fall 3 rise 2 backup
Server directive flags:
check— enable health checkinginter 5s— check every 5 secondsfall 3— mark down after 3 failuresrise 2— mark up after 2 successesweight 2— receives 2× traffic in round-robinbackup— only used when all primary servers are down
Balance Algorithms
| Algorithm | Config | Best For |
|---|---|---|
| Round-robin | balance roundrobin | Stateless, equal-capacity servers |
| Least connections | balance leastconn | Long-lived connections (DB, WebSockets) |
| Source IP hash | balance source | Stateful apps needing soft affinity |
| URI hash | balance uri | Cache servers (same URI → same server) |
| Random | balance random | Large clusters, simple distribution |
backend db_servers
balance leastconn # route new connections to least-busy server
...
backend cache_servers
balance uri # same URL always hits same cache server
hash-type consistent
...
ACLs and Routing
HAProxy ACLs allow conditional routing based on any request attribute:
frontend https_in
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
# Define ACLs
acl is_api hdr(host) -i api.example.com
acl is_websocket hdr(Upgrade) -i websocket
acl is_static path_beg /static/ /assets/
acl is_post method POST
acl is_large req.body_len gt 10000000 # > 10MB
# Apply routing
use_backend ws_servers if is_websocket
use_backend static_servers if is_static
use_backend api_servers if is_api
default_backend web_servers
Rate Limiting
frontend https_in
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
# Stick table: track IPs, store connection rate over 30s
stick-table type ip size 200k expire 60s store conn_cur,conn_rate(30s),http_req_rate(10s)
# Track source IP
http-request track-sc0 src
# Deny if > 200 req/10s or > 50 concurrent connections
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 200 }
http-request deny deny_status 429 if { sc_conn_cur(0) gt 50 }
default_backend web_servers
WebSocket Support
frontend ws_front
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
option http-server-close
option forwardfor
# Detect WebSocket upgrade
acl is_websocket hdr(Upgrade) -i websocket
use_backend ws_servers if is_websocket
default_backend web_servers
backend ws_servers
balance leastconn # leastconn is better for long-lived WS connections
option http-server-close
timeout tunnel 1h # keep tunnel open for long WebSocket sessions
server ws01 10.0.1.10:3000 check
server ws02 10.0.1.11:3000 check
TCP Load Balancing (Layer 4)
For non-HTTP protocols (MySQL, PostgreSQL, Redis, raw TCP):
listen mysql_cluster
bind *:3306
mode tcp
balance leastconn
option tcp-check
server db01 10.0.1.20:3306 check
server db02 10.0.1.21:3306 check backup
listen redis
bind *:6379
mode tcp
balance first # always route to first available server
option tcp-check
server redis01 10.0.1.30:6379 check
server redis02 10.0.1.31:6379 check backup
Stats Dashboard
frontend stats
bind *:8404
mode http
stats enable
stats uri /stats
stats refresh 10s
stats auth admin:securepassword
stats admin if TRUE # allow enable/disable servers from UI
stats hide-version
stats show-legends
stats show-node
Access at http://your-lb:8404/stats. The dashboard shows:
- Request rates, error rates per frontend/backend
- Active/down server counts
- Queue depths
- Session counts
Runtime Socket API
Manage HAProxy without restarts:
# Show all server states
echo "show servers state" | sudo socat stdio /run/haproxy/admin.sock
# Disable a server gracefully (drain existing connections)
echo "set server web_servers/web03 state drain" | sudo socat stdio /run/haproxy/admin.sock
# Put server in maintenance (reject new and drop existing)
echo "set server web_servers/web03 state maint" | sudo socat stdio /run/haproxy/admin.sock
# Re-enable server
echo "set server web_servers/web03 state ready" | sudo socat stdio /run/haproxy/admin.sock
# Change server weight at runtime
echo "set server web_servers/web01 weight 2" | sudo socat stdio /run/haproxy/admin.sock
# Show current traffic stats (CSV)
echo "show stat" | sudo socat stdio /run/haproxy/admin.sock | cut -d, -f1,2,18,19,48
# Reload config without dropping connections (HAProxy 2.0+)
sudo haproxy -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid)
Logging
Configure HAProxy to log to rsyslog:
# /etc/rsyslog.d/49-haproxy.conf
local0.* /var/log/haproxy.log
local1.* /var/log/haproxy.log
# Prevent duplicates in syslog
& stop
sudo systemctl restart rsyslog
Custom log format for structured logging:
defaults
log-format '{"ts":"%t","client":"%ci:%cp","frontend":"%ft","backend":"%b/%s","status":%ST,"req_time":%Tr,"conn_time":%Tc,"bytes":%B,"request":"%r"}'
Quick Reference
# Validate config
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# Reload (hot reload, no dropped connections)
sudo systemctl reload haproxy
# Check active connections
echo "show info" | sudo socat stdio /run/haproxy/admin.sock | grep CurrConns
# Top backends by request rate
echo "show stat" | sudo socat stdio /run/haproxy/admin.sock | awk -F, 'NR>1 {print $1,$2,$48}' | sort -k3 -rn | head
Was this article helpful?
Network & Traffic Engineer
Packets don't lie. I design and troubleshoot the network layer that everything else depends on — Nginx, Envoy, HAProxy, DNS, CDNs, and everything in between. If it touches a socket, it's my problem.
Related Articles
HAProxy + Keepalived: Production HA Load Balancer Setup
Step-by-step guide to building a highly available load balancer pair with HAProxy and Keepalived — covering the full stack from VIP configuration to health checks, stats, and SSL termination.
HAProxy Advanced: SSL Termination, SNI Routing, and mTLS
Advanced HAProxy SSL configuration — multi-domain SNI routing from a single frontend, mutual TLS (mTLS) for service-to-service authentication, certificate management, and OCSP stapling.
Envoy Proxy: Architecture, xDS Configuration, and Getting Started
An introduction to Envoy Proxy's architecture — listeners, clusters, filters, and the xDS dynamic configuration API. Covers static configuration for standalone use and how Envoy fits into service meshes like Istio.
Keepalived and VRRP: Building High-Availability Failover for Linux Services
How to configure Keepalived for automatic failover using VRRP — setting up master/backup pairs, virtual IPs, health-check scripts, and combining it with HAProxy or Nginx for zero-downtime load balancer HA.
Apache mod_proxy: Reverse Proxy, Load Balancing, and WebSocket Support
How to use Apache httpd as a reverse proxy with mod_proxy — proxying to backend services, load balancing across multiple upstreams, WebSocket proxying, and health check configuration.
Apache httpd: Virtual Hosts, SSL/TLS, and URL Rewriting in Production
How to configure Apache httpd for production use — name-based virtual hosts, SSL/TLS with Let's Encrypt, HTTP to HTTPS redirects, mod_rewrite rules, and performance tuning with MPM.
More in Nginx
View all →Envoy Traffic Management: Retries, Timeouts, Canary Deployments, and Rate Limiting
Advanced Envoy traffic management — configuring retries with exponential backoff, per-request timeouts, weighted canary routing, global rate limiting, and fault injection for resilience testing.
Fix Nginx 502 Bad Gateway Behind a Reverse Proxy
Diagnose and fix Nginx 502 Bad Gateway errors when proxying to upstream backends — check sockets, timeouts, and upstream health.
Fix Nginx 'Too Many Open Files' Error
Resolve Nginx 'Too many open files' errors by increasing worker_rlimit_nofile, system ulimits, and kernel file descriptor limits.
Nginx Load Balancing: Round Robin, Least Conn, and IP Hash
Configure Nginx upstream load balancing with round robin, least connections, and IP hash strategies including health checks and failover.