Our earlier Nginx reverse proxy guide covers routing traffic to a single backend app. HAProxy is purpose-built for the next step: spreading traffic across multiple backend servers, health-checking each one, and automatically routing around a server that goes down.
This guide sets up a two-node backend pool behind a single HAProxy load balancer, verifies round-robin distribution, and tests real failover by taking a backend offline.
Step 1: Install HAProxy
On the load balancer host: sudo apt install haproxy (Ubuntu/Debian) or sudo dnf install haproxy (AlmaLinux). Confirm the version with haproxy -v — this guide assumes HAProxy 2.8+.

Step 2: Plan Your Backend Pool
Identify the servers HAProxy will distribute traffic to — in this example, web01 (192.168.1.101) and web02 (192.168.1.102), both running identical web apps on port 80. Confirm each is independently reachable (curl web01:80) before wiring up the load balancer.

Step 3: Configure the Frontend
Edit /etc/haproxy/haproxy.cfg and add a frontend web_front block that binds port 80 and points default_backend at the backend pool you’re about to define. The frontend is the entry point clients actually connect to.

Step 4: Configure the Backend With Health Checks
Add a backend web_back block with balance roundrobin and a server line per host, each ending in check — this tells HAProxy to actively health-check that server (default: TCP connect check every 2s) and automatically pull it out of rotation if it stops responding.

Step 5: Validate and Reload the Configuration
Always check syntax before reloading a live load balancer: haproxy -c -f /etc/haproxy/haproxy.cfg. Once it reports Configuration file is valid, reload with systemctl reload haproxy — this applies the new config without dropping existing connections.

Step 6: Enable the Stats Page
Add a listen stats block bound to a separate port (e.g. *:8404) with stats enable and a basic-auth stats auth line. Browse to http://lb01:8404/stats to see live per-backend session counts, health status, and error rates — your first stop when diagnosing anything.

Step 7: Test Round-Robin Distribution
From a client machine, hit the load balancer repeatedly and confirm requests alternate between backends: for i in 1 2 3 4; do curl -s lb01/hostname; done should show web01, web02, web01, web02 — proof traffic is actually being spread, not pinned to one server.

Step 8: Test Failover
Stop the app on one backend (systemctl stop nginx on web02) and watch the stats page mark it DOWN within one health-check interval. Repeat the round-robin test — every request should now land on web01 only, with zero client-visible errors. This is the actual payoff: a backend can die and users don’t notice.

Example haproxy.cfg
frontend web_front
bind *:80
default_backend web_back
backend web_back
balance roundrobin
option httpchk GET /healthz
server web01 192.168.1.101:80 check
server web02 192.168.1.102:80 check
listen stats
bind *:8404
stats enable
stats uri /stats
stats auth admin:changeme
Next steps for production
- Switch
option httpchkto a real health endpoint your app exposes, not just a TCP connect check — an app that’s hung but still accepting TCP connections will otherwise stay in rotation - Terminate SSL at HAProxy with a
bind *:443 ssl crt /etc/haproxy/certs/site.pemline — pair with our Certbot guide for the certificate - HAProxy itself is a single point of failure unless you run two instances with keepalived and a floating VIP — worth doing once this box matters for production traffic
Related tutorials
Terminal screenshots are original illustrations created for Gnome IT Solutions (blog.gnomeitsolutions.com).