Stackbook Logo
← Back to Tools

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.

4 min read

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.