HAProxy can feel like a black box, but it’s actually designed to be incredibly transparent and controllable.
Let’s see HAProxy in action. Imagine you have two backend web servers, web1 on 192.168.1.10:80 and web2 on 192.168.1.11:80. You want HAProxy to distribute traffic between them. Here’s a minimal haproxy.cfg:
global
log /dev/log local0
daemon
defaults
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http_in
bind *:80
default_backend http_back
backend http_back
balance roundrobin
server web1 192.168.1.10:80 check
server web2 192.168.1.11:80 check
When a client connects to HAProxy on port 80, HAProxy inspects the incoming HTTP request. Based on the balance roundrobin setting in the backend http_back section, it picks the next server in line to forward that request to. check tells HAProxy to periodically ping web1 and web2 to ensure they’re healthy, and it will stop sending traffic to any server that fails its checks.
This setup solves the problem of needing a single, reliable entry point for your web application, even if your backend servers are numerous or prone to occasional failures. HAProxy acts as a traffic cop, directing incoming requests to available servers and ensuring high availability. The global section sets up logging and runs HAProxy as a background process. defaults provides common settings for both frontends and backends, like connection timeouts. The frontend http_in defines where HAProxy listens for incoming traffic (*:80 means all interfaces, port 80) and which backend to use (default_backend http_back). The backend http_back defines the pool of servers that will handle the requests and how traffic is distributed among them.
The balance directive is your primary tool for controlling distribution. roundrobin is simple and equitable, but for more sophisticated needs, you can use leastconn (sends traffic to the server with the fewest active connections), source (hashes the client IP to consistently send a client to the same server, good for sticky sessions without cookies), or even custom algorithms. The server lines specify the actual backend servers, their IP addresses and ports, and crucially, the check option. Without check, HAProxy would happily send traffic to a dead server, defeating the purpose of load balancing.
HAProxy’s ability to perform health checks is more than just a simple ping. It can be configured to expect specific responses. For instance, instead of just check, you could use httpchk GET /health. This tells HAProxy to make an HTTP GET request to /health on the backend server and expect a 2xx or 3xx status code. If it gets anything else, or a timeout, the server is marked down. This is far more robust than a basic TCP check.
Many people overlook the power of HAProxy’s statistics page. By adding a listen stats section to your haproxy.cfg, you get a live, interactive dashboard:
listen stats
bind *:8080
mode http
stats enable
stats uri /haproxy?stats
stats auth admin:password
Accessing http://your-haproxy-ip:8080/haproxy?stats in your browser (after replacing admin:password with your chosen credentials) will show you real-time connection counts, response times, error rates, and server status for all your frontends and backends. It’s an invaluable tool for understanding traffic flow and diagnosing issues.
When you need to ensure a client consistently hits the same backend server, often for applications that store session state locally, you’ll use HAProxy’s cookie-based session persistence. Instead of balance source, you’d add cookie SERVERID to your backend servers and then use cookie SERVERID insert indirect in your frontend. This tells HAProxy to add a SERVERID cookie to responses going to the client. On subsequent requests from that client, HAProxy will look for this cookie and route the request to the server that originally set it, even if other load balancing algorithms are also defined.
The next concept you’ll encounter is fine-tuning HAProxy’s performance and security, often involving TLS termination and advanced request routing.