| |

LFCE 62 🐧 Load Balancing with HAProxy

⏱️ Estimated read: 13 min | |

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

SectionPurpose
globalProcess-wide settings
defaultsDefaults for frontends and backends
frontendListening address and routing rules
backendServer pool and health checks
listenCombined frontend and backend

Balance Algorithms

DirectiveBehavior
balance roundrobinRound-robin (default)
balance leastconnLeast connections
balance sourceIP hash
balance uriURI hash
balance randomRandom selection

Health Check Parameters

ParameterPurpose
option httpchk GET /pathHTTP health check
http-check expect status 200Expected response
checkEnable check on server
inter 5sInterval between checks
fall 3Failures before DOWN
rise 2Successes before UP

Statistics Interface

DirectivePurpose
stats enableEnable the page
stats uri /statsPath
stats refresh 10sAuto-refresh interval
stats auth user:passBasic 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

PitfallWhy It HappensFix
Backend stays in rotation after failureHealth check not enabledAdd check and option httpchk
Statistics page inaccessibleWrong bind or missing stats enableVerify the listen stats section
Uneven load distributionRound-robin with variable requestsUse leastconn
Session lossNo persistenceUse balance source or a shared store
Configuration errorMissing section or wrong syntaxhaproxy -c reports the line
Slow failoverHealth-check interval too longReduce 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

ItemValue
Configuration file/etc/haproxy/haproxy.cfg
Sectionsglobal, defaults, frontend, backend, listen
Default algorithmroundrobin
Least-connectionsbalance leastconn
Session persistencebalance source
Active health checkoption httpchk
Check parameterscheck inter 5s fall 3 rise 2
Statistics pagestats enable + stats uri
Statistics authstats auth user:pass
Validationhaproxy -c -f /etc/haproxy/haproxy.cfg

Key takeaways:

  • HAProxy separates the frontend and the backend. The frontend section defines the listening address and the routing ACLs. The backend section defines the server pool and the health checks. The default_backend and use_backend directives connect them .
  • The balance directive selects the algorithm. roundrobin is the default, leastconn is for variable request durations, and source provides session persistence. The weight parameter gives a server a larger share of the requests .
  • Active health checks are more reliable than passive checks. The option httpchk directive 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 2 parameters control the check. The inter is the interval, the fall is the number of failures before DOWN, and the rise is 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 enable and stats uri directives serve a page that lists every backend, its state, its active sessions, and its health-check result. The stats auth directive 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.


Was this helpful?


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!