LFCE 62 🐧 Load Balancing with HAProxy
HAProxy is a dedicated Layer 4 and Layer 7 load balancer. Where Nginx is a web server that can also proxy, HAProxy is a proxy first and does not serve files. It is designed to handle high connection counts with low latency, and it exposes a statistics interface that shows the state of every backend in real time. The previous chapter covered Nginx’s upstream block and its passive health checks; this chapter covers the HAProxy equivalent: the frontend, backend, and global sections, the balance directive that selects the algorithm, the option httpchk that performs active health checks, and the stats interface that shows the load balancer’s state.
The configuration is organized into sections. The global section sets process-wide parameters. The defaults section sets values that apply to all frontend and backend sections. The frontend section defines the listening address and the rules that direct requests to a backend. The backend section defines the pool of servers and the health-check behavior . This is a different structure from Nginx’s single-file approach, but the concepts—the algorithm, the health check, the connection limits—are the same.
Key point: The balance directive selects the algorithm. The default is roundrobin. The leastconn algorithm is appropriate for variable request durations, and the source algorithm provides session persistence by hashing the client’s IP address . The option httpchk directive enables active HTTP health checks, which are more reliable than Nginx’s passive checks because they probe the backend on a timer rather than waiting for a client request to fail . The stats interface shows the state of every backend, including the number of active sessions and the health-check result.
Why HAProxy’s section-based configuration differs from Nginx
The frontend-backend separation.
In Nginx, the upstream block and the server block that references it are two separate blocks in the same file, and the relationship is established by the proxy_pass directive. In HAProxy, the frontend section defines the listening address and the ACLs that route requests, and the backend section defines the pool and the health checks. The use_backend or default_backend directive in the frontend points to the backend by name . This separation makes the routing rules and the pool configuration independent, and it allows multiple frontends to share a backend or one frontend to route to multiple backends based on ACLs.
The active health-check problem.
Nginx’s open-source edition does not have active health checks. HAProxy does. The option httpchk directive tells HAProxy to send an HTTP request to a configured path on each backend at a regular interval. A backend that fails the check is marked DOWN and removed from rotation; a backend that passes again is marked UP and returned to the pool. The check keyword on each server line enables the check, and the inter parameter sets the interval . This is more reliable than passive checks because it detects a failed backend before a client request is routed to it.
The statistics problem.
HAProxy exposes a statistics page that shows the state of every backend, the number of active and queued sessions, the health-check status, and the bytes transferred. The page is enabled with the stats enable directive and the stats uri directive that sets the path. The page can be protected with stats auth . Nginx does not have an equivalent built-in page; the stub_status module provides a minimal status endpoint, but it does not show per-backend health.
a. The frontend, backend, and global sections
A minimal HAProxy configuration has a global section, a defaults section, a frontend section, and a backend section.
# /etc/haproxy/haproxy.cfg
global
daemon
maxconn 4096
log stdout format raw local0
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
option httplog
frontend web_frontend
bind *:80
default_backend web_servers
backend web_servers
balance roundrobin
server web1 10.0.0.1:8080 check
server web2 10.0.0.2:8080 check
The global section sets the daemon mode and the maximum number of connections. The defaults section sets the mode, the timeouts, and the log format. The frontend section binds to port 80 and sends the requests to the web_servers backend. The backend section defines the two servers with the check keyword enabled . The mode http directive tells HAProxy to operate at Layer 7; the mode tcp directive would make it a Layer 4 proxy.
The bind directive in the frontend can listen on a specific address or on all addresses. The default_backend directive names the backend that receives the requests when no other rule matches. The use_backend directive, combined with an ACL, routes requests to different backends based on the request content.
frontend web_frontend
bind *:80
acl is_api path_beg /api
use_backend api_servers if is_api
default_backend web_servers
backend web_servers
balance roundrobin
server web1 10.0.0.1:8080 check
server web2 10.0.0.2:8080 check
backend api_servers
balance leastconn
server api1 10.0.0.3:8080 check
server api2 10.0.0.4:8080 check
The acl is_api path_beg /api defines a rule that matches requests whose path begins with /api. The use_backend api_servers if is_api directive routes those requests to the API backend. All other requests go to the web_servers backend . The two backends can use different algorithms and different health checks.
b. The balance directive and load-balancing algorithms
The balance directive in the backend section selects the algorithm. The default is roundrobin, which distributes requests evenly across the servers.
backend web_servers
balance roundrobin
server web1 10.0.0.1:8080 check weight 3
server web2 10.0.0.2:8080 check weight 1
The weight parameter gives a server a larger share of the requests. A server with weight 3 receives three times as many requests as a server with weight 1 . The weights are supported by roundrobin and leastconn; the source algorithm uses the hash of the client’s IP and ignores the weights.
The leastconn algorithm selects the server with the fewest active connections. It is appropriate when the requests have variable durations, because it avoids sending new requests to a server that is already processing several slow requests.
backend web_servers
balance leastconn
server web1 10.0.0.1:8080 check
server web2 10.0.0.2:8080 check
The source algorithm hashes the client’s IP address and maps it to a server. Requests from the same client always reach the same server, which provides session persistence for applications that store state locally.
backend web_servers
balance source
server web1 10.0.0.1:8080 check
server web2 10.0.0.2:8080 check
The source algorithm is a workaround for session state. A shared session store is more reliable, because it allows the load balancer to distribute requests by any algorithm without losing the session.
c. Active health checks and the statistics interface
The option httpchk directive enables active HTTP health checks. The directive specifies the method and the path that HAProxy requests on each backend.
backend web_servers
balance leastconn
option httpchk GET /health
http-check expect status 200
server web1 10.0.0.1:8080 check inter 5s fall 3 rise 2
server web2 10.0.0.2:8080 check inter 5s fall 3 rise 2
The option httpchk GET /health tells HAProxy to send a GET /health request to each backend. The http-check expect status 200 directive marks the backend as healthy only if the response status is 200. The inter 5s parameter sets the interval between checks; the fall 3 parameter is the number of consecutive failures before the backend is marked DOWN; the rise 2 parameter is the number of consecutive successes before it is marked UP again . The check keyword on each server line enables the check.
The statistics interface is enabled in the frontend or in a separate listen section. The stats enable directive turns it on, and the stats uri directive sets the path.
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
stats auth admin:secret
The listen stats section binds to port 8404 and serves the statistics page at /stats. The stats refresh 10s directive reloads the page every ten seconds. The stats auth directive requires HTTP basic authentication, which is necessary if the page is reachable from an untrusted network . The page shows each backend’s state, the number of active sessions, the health-check status, and the bytes transferred.
Complete Example Session
# ============================================
# PART 1: INSTALL HAPROXY
# ============================================
sudo apt install haproxy
# Or: sudo dnf install haproxy
# ============================================
# PART 2: START TEST BACKENDS
# ============================================
python3 -m http.server 8080 --directory /var/www/backend1 &
python3 -m http.server 8081 --directory /var/www/backend2 &
# ============================================
# PART 3: WRITE THE CONFIGURATION
# ============================================
sudo tee /etc/haproxy/haproxy.cfg > /dev/null <<'EOF'
global
daemon
maxconn 4096
log stdout format raw local0
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
option httplog
frontend web_frontend
bind *:80
default_backend web_servers
backend web_servers
balance leastconn
option httpchk GET /
http-check expect status 200
server web1 127.0.0.1:8080 check inter 5s fall 3 rise 2
server web2 127.0.0.1:8081 check inter 5s fall 3 rise 2
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
EOF
# ============================================
# PART 4: VALIDATE THE CONFIGURATION
# ============================================
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# Output: Configuration file is valid
# ============================================
# PART 5: START HAPROXY
# ============================================
sudo systemctl restart haproxy
sudo systemctl status haproxy
# ============================================
# PART 6: TEST LOAD BALANCING
# ============================================
for i in {1..10}; do curl -s http://localhost; done
# The responses alternate between backend1 and backend2
# ============================================
# PART 7: CHECK THE STATISTICS PAGE
# ============================================
curl -s http://localhost:8404/stats | grep -E "web1|web2"
# Output shows the state of each backend
# ============================================
# PART 8: TEST FAILOVER
# ============================================
kill %1 # Stop the first backend
sleep 10 # Wait for the health check to mark it DOWN
curl -I http://localhost
# HAProxy routes all requests to the second backend
# ============================================
# PART 9: VERIFY THE BACKEND IS DOWN
# ============================================
curl -s http://localhost:8404/stats | grep web1
# Output shows web1 in DOWN state
# ============================================
# PART 10: CLEAN UP
# ============================================
kill %1 %2
sudo systemctl stop haproxy
The ten parts demonstrate the HAProxy load-balancing setup: the installation, the test backends, the configuration with leastconn and active health checks, the validation, the service start, the load-balancing test, the statistics page, the failover test, the backend state verification, and the cleanup. The critical verification is Part 9: the statistics page shows the failed backend in the DOWN state, which confirms that the health check is working.
Quick Reference
Configuration Sections
| Section | Purpose |
|---|---|
global | Process-wide settings |
defaults | Defaults for frontends and backends |
frontend | Listening address and routing rules |
backend | Server pool and health checks |
listen | Combined frontend and backend |
Balance Algorithms
| Directive | Behavior |
|---|---|
balance roundrobin | Round-robin (default) |
balance leastconn | Least connections |
balance source | IP hash |
balance uri | URI hash |
balance random | Random selection |
Health Check Parameters
| Parameter | Purpose |
|---|---|
option httpchk GET /path | HTTP health check |
http-check expect status 200 | Expected response |
check | Enable check on server |
inter 5s | Interval between checks |
fall 3 | Failures before DOWN |
rise 2 | Successes before UP |
Statistics Interface
| Directive | Purpose |
|---|---|
stats enable | Enable the page |
stats uri /stats | Path |
stats refresh 10s | Auto-refresh interval |
stats auth user:pass | Basic authentication |
Best Practices
✅ Do This:
# Use leastconn for variable request durations
balance leastconn # ✅
# Enable active HTTP health checks
option httpchk GET /health
http-check expect status 200 # ✅
# Set the check parameters
server web1 10.0.0.1:8080 check inter 5s fall 3 rise 2 # ✅
# Protect the statistics page
stats auth admin:secret # ✅
# Validate before restarting
haproxy -c -f /etc/haproxy/haproxy.cfg # ✅
# Use ACLs for path-based routing
acl is_api path_beg /api
use_backend api_servers if is_api # ✅
❌ Don’t Do This:
# Use roundrobin for variable request durations
balance roundrobin # Slower backend accumulates connections # ❌
# Omit the health check
server web1 10.0.0.1:8080 # Failed backend stays in pool # ❌
# Expose the statistics page without authentication
stats enable; stats uri /stats # Publicly accessible # ❌
# Use source without considering NAT
balance source # Multiple clients behind NAT share one IP # ❌
# Forget the timeouts
# Long-lived connections hang without them # ❌
# Restart without validation
systemctl restart haproxy # Syntax error causes outage # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Backend stays in rotation after failure | Health check not enabled | Add check and option httpchk |
| Statistics page inaccessible | Wrong bind or missing stats enable | Verify the listen stats section |
| Uneven load distribution | Round-robin with variable requests | Use leastconn |
| Session loss | No persistence | Use balance source or a shared store |
| Configuration error | Missing section or wrong syntax | haproxy -c reports the line |
| Slow failover | Health-check interval too long | Reduce inter |
Real-World Examples
1. Round-robin
balance roundrobin
2. Least-connections
balance leastconn
3. Source hash
balance source
4. Weighted server
server web1 10.0.0.1:8080 check weight 3
5. HTTP health check
option httpchk GET /health
http-check expect status 200
6. Check parameters
server web1 10.0.0.1:8080 check inter 5s fall 3 rise 2
7. Statistics page
listen stats
bind *:8404
stats enable
stats uri /stats
8. Path-based routing
acl is_api path_beg /api
use_backend api_servers if is_api
9. Validate configuration
haproxy -c -f /etc/haproxy/haproxy.cfg
10. Test load balancing
for i in {1..10}; do curl -s http://localhost; done
Visual
HAProxy Architecture
┌──────────────────────────────────────────────────────────────┐
│ FRONTEND → BACKEND → SERVERS │
│ │
│ Client │
│ ┌─────────┐ │
│ │ Browser │ │
│ └────┬────┘ │
│ │ HTTP │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ frontend web_frontend │ │
│ │ bind *:80 │ │
│ │ default_backend web_servers │ │
│ └──────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ backend web_servers │ │
│ │ balance leastconn │ │
│ │ option httpchk GET /health │ │
│ │ server web1 10.0.0.1:8080 check │ │
│ │ server web2 10.0.0.2:8080 check │ │
│ └──────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ┌──────────────┴──────────────┐ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ web1 │ │ web2 │ │
│ │ :8080 │ │ :8080 │ │
│ └─────────────────┘ └─────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Active Health Check
┌──────────────────────────────────────────────────────────────┐
│ HAPROXY PROBES EACH BACKEND ON A TIMER │
│ │
│ Every 5 seconds (inter 5s): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ HAProxy → GET /health → web1 │ │
│ │ HAProxy → GET /health → web2 │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ web1 returns 500 three times in a row (fall 3): │
│ → web1 is marked DOWN │
│ → Removed from rotation │
│ │
│ web1 returns 200 two times in a row (rise 2): │
│ → web1 is marked UP │
│ → Returned to rotation │
└──────────────────────────────────────────────────────────────┘
Statistics Page
┌──────────────────────────────────────────────────────────────┐
│ THE STATS PAGE SHOWS EVERY BACKEND'S STATE │
│ │
│ http://haproxy:8404/stats │
│ │
│ ┌──────────┬────────┬────────┬────────┬──────────────────┐ │
│ │ Server │ State │ Active │ Weight │ Health Check │ │
│ ├──────────┼────────┼────────┼────────┼──────────────────┤ │
│ │ web1 │ UP │ 5 │ 1 │ 200 OK │ │
│ │ web2 │ DOWN │ 0 │ 1 │ Connection refused│ │
│ └──────────┴────────┴────────┴────────┴──────────────────┘ │
│ │
│ The page refreshes every 10 seconds (stats refresh 10s). │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Configuration file | /etc/haproxy/haproxy.cfg |
| Sections | global, defaults, frontend, backend, listen |
| Default algorithm | roundrobin |
| Least-connections | balance leastconn |
| Session persistence | balance source |
| Active health check | option httpchk |
| Check parameters | check inter 5s fall 3 rise 2 |
| Statistics page | stats enable + stats uri |
| Statistics auth | stats auth user:pass |
| Validation | haproxy -c -f /etc/haproxy/haproxy.cfg |
Key takeaways:
- HAProxy separates the frontend and the backend. The
frontendsection defines the listening address and the routing ACLs. Thebackendsection defines the server pool and the health checks. Thedefault_backendanduse_backenddirectives connect them . - The
balancedirective selects the algorithm.roundrobinis the default,leastconnis for variable request durations, andsourceprovides session persistence. Theweightparameter gives a server a larger share of the requests . - Active health checks are more reliable than passive checks. The
option httpchkdirective probes each backend on a timer. A backend that fails the check is marked DOWN and removed from rotation; a backend that passes again is marked UP and returned to the pool . - The
check inter 5s fall 3 rise 2parameters control the check. Theinteris the interval, thefallis the number of failures before DOWN, and theriseis the number of successes before UP. The defaults are usually too lenient for a fast failover. - The statistics page shows the load balancer’s state. The
stats enableandstats uridirectives serve a page that lists every backend, its state, its active sessions, and its health-check result. Thestats authdirective protects it with basic authentication . - The configuration is validated with
haproxy -c. The validation command parses the configuration and reports syntax errors before the restart. A configuration error prevents the service from starting.
Remember: HAProxy is the dedicated load balancer. The frontend defines what the client connects to, the backend defines the servers and the health checks, and the balance directive selects the algorithm. Active health checks probe the backends on a timer, and the statistics page shows their state. Validate with haproxy -c, restart with systemctl restart haproxy, and test with curl. The load balancer is only as good as its health checks; a backend that fails must be removed from rotation before a client request reaches it.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!