HAProxy Load Balancing Guide
Set up HAProxy for reliable TCP and HTTP load balancing: frontends, backends, health checks, ACL routing, TLS termination, stats and tuning.
What it is and when to use it
HAProxy is a fast, battle-tested load balancer and proxy for TCP and HTTP traffic. It is known for predictable low latency, rich health checking, detailed statistics and very efficient resource use. It runs the front door of many high traffic sites.
Use HAProxy to distribute traffic over application or database servers, terminate TLS, route by host or path with ACLs, and fail over automatically when a server stops responding. It works well for non-HTTP protocols such as PostgreSQL or MQTT where Layer 4 balancing is enough.
Quick start
Install and manage the service.
sudo apt install -y haproxy
sudo haproxy -c -f /etc/haproxy/haproxy.cfg # validate
sudo systemctl reload haproxy # graceful reload
A minimal haproxy.cfg for a web tier:
global
log /dev/log local0
maxconn 20000
user haproxy
group haproxy
defaults
mode http
log global
option httplog
option dontlognull
option forwardfor
timeout connect 5s
timeout client 30s
timeout server 30s
retries 3
frontend fe_http
bind *:80
default_backend be_web
backend be_web
balance roundrobin
option httpchk GET /healthz
http-check expect status 200
server web1 10.0.1.10:3000 check inter 2s fall 3 rise 2
server web2 10.0.1.11:3000 check inter 2s fall 3 rise 2
Core concepts
Frontends accept connections on bind addresses; backends hold server pools; defaults set shared options. The listen section combines both for simple cases. Mode selects Layer 7 (http) or Layer 4 (tcp).
Balancing algorithms include roundrobin, leastconn (best for long-lived connections), source (client IP hashing), uri, and hdr for header-based hashing. Stick tables allow session persistence.
Health checks. The check keyword enables TCP checks; httpchk makes them application aware. The fall and rise values control how many consecutive failures or successes flip a server's state.
ACLs and routing match requests by host, path, method, headers or source address.
frontend fe_https
bind *:443 ssl crt /etc/haproxy/certs/site.pem alpn h2,http/1.1
http-request set-header X-Forwarded-Proto https
acl is_api path_beg /api/
acl is_app hdr(host) -i app.example.com
acl is_blocked src 203.0.113.0/24
http-request deny if is_blocked
use_backend be_api if is_api
use_backend be_web if is_app
default_backend be_web
frontend fe_redirect
bind *:80
http-request redirect scheme https code 301 unless { ssl_fc }
The pem file should contain the certificate chain followed by the private key.
Rate limiting with stick tables.
frontend fe_https
stick-table type ip size 100k expire 30s store http_req_rate(10s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }
TCP mode for databases:
listen pg
bind *:5432
mode tcp
option tcp-check
balance leastconn
server pg1 10.0.2.10:5432 check
server pg2 10.0.2.11:5432 check backup
Stats and runtime control. Enable a protected stats page and the admin socket.
listen stats
bind 127.0.0.1:8404
mode http
stats enable
stats uri /stats
stats refresh 10s
echo "show stat" | sudo socat stdio /run/haproxy/admin.sock
echo "set server be_web/web1 state drain" | sudo socat stdio /run/haproxy/admin.sock
Draining a server through the runtime API lets you do rolling deployments without dropped requests.
Pitfalls
- Missing or too-short timeouts produce warnings and hung connections. Always define connect, client and server timeouts in defaults.
- A single HAProxy node is itself a single point of failure. Pair two nodes with keepalived and a virtual IP, or put them behind a cloud load balancer.
- Behind another proxy, the client IP is hidden. Use the PROXY protocol with send-proxy and accept-proxy rather than trusting forwarded headers blindly.
- Health check endpoints that only return 200 even when dependencies are down give false confidence. Make them meaningful but cheap.
- Long-lived WebSocket or streaming traffic needs timeout tunnel configured or connections get cut.
- Reload is graceful but old processes linger while connections finish. Watch for many processes after frequent reloads.
- Test with haproxy -c every time, as a bad config blocks the reload.
How it fits an architecture
HAProxy is a strong choice as the entry tier: a pair of nodes terminating TLS, applying ACL-based routing, and balancing across web or API pools, or across database replicas at Layer 4. Configuration is easy to template with Ansible or Terraform outputs. In Kubernetes there is an HAProxy Ingress controller, while service meshes such as Istio use Envoy instead for east-west traffic. Pair it with Prometheus, using the built-in exporter, for dashboards in Grafana.
Key takeaways
- HAProxy offers efficient Layer 4 and Layer 7 load balancing with excellent health checks.
- Structure config as defaults, frontends and backends, and route using ACLs.
- Use leastconn for long-lived connections and stick tables for persistence or rate limiting.
- Make the proxy tier redundant and validate every config before reload.
- Use the runtime socket to drain servers during rolling deployments.