HAProxy Load Balancing
Applies to CentOS Stream 9 & 10 / AlmaLinux 9.x & 10.x / Rocky Linux 9.x & 10.x
HAProxy is a high-performance TCP/HTTP load balancer and reverse proxy. When a single backend server can no longer handle the traffic, or when you need to keep a service available during upgrades and outages, HAProxy distributes requests across multiple backends to achieve horizontal scaling and high availability. This guide walks you through building layer-4/layer-7 load balancing from scratch on an EL system.
What You Will Learn
Section titled “What You Will Learn”- Install HAProxy and understand the four core sections of its configuration
- Configure a frontend and backend to distribute traffic across multiple servers
- Use the
roundrobin/leastconnscheduling algorithms with health checks - Enable the stats page to observe backend state
- Handle SELinux and firewalld permissions
- Configure TLS termination and HTTP→HTTPS redirection
- Understand how to pair HAProxy with Keepalived so HAProxy itself is highly available
Prerequisites
Section titled “Prerequisites”- A system running EL 9.x or EL 10.x to act as the load balancer
- At least two reachable backend web servers (this guide uses
10.0.0.11/10.0.0.12) - sudo privileges
Versions Across Distributions
Section titled “Versions Across Distributions”Install HAProxy
Section titled “Install HAProxy”HAProxy is provided in the AppStream repository, so you can install it directly:
$ sudo dnf install haproxyConfirm the version:
$ haproxy -vHAProxy version 2.4.x ...Configuration File Structure
Section titled “Configuration File Structure”HAProxy’s main configuration file is /etc/haproxy/haproxy.cfg, made up of four core sections:
| Section | Purpose |
|---|---|
global | Process-level settings: logging, run-as user, max connections, tuning parameters |
defaults | Provides default values for the frontend/backend sections that follow, avoiding repetition |
frontend | Defines the address and port to listen on, and which backend to hand traffic to |
backend | Defines the pool of backend servers, the scheduling algorithm, and health checks |
The frontend receives client requests, the backend holds the real servers, and the two are linked by name.
A Complete Load Balancing Example
Section titled “A Complete Load Balancing Example”The configuration below listens on port 80 and distributes HTTP requests in round-robin fashion across two backend web servers. Edit /etc/haproxy/haproxy.cfg:
global log 127.0.0.1 local2 chroot /var/lib/haproxy pidfile /var/run/haproxy.pid maxconn 4000 user haproxy group haproxy stats socket /var/run/haproxy.sock mode 660 level admin daemon
defaults mode http log global option httplog option dontlognull retries 3 timeout connect 5s timeout client 30s timeout server 30s timeout http-request 10s
frontend web_front bind *:80 default_backend web_servers
backend web_servers balance roundrobin option httpchk GET / server web1 10.0.0.11:80 check server web2 10.0.0.12:80 checkA few key points:
mode httpworks at layer 7 (HTTP) and can parse HTTP headers; changing it tomode tcpgives a layer-4 passthrough, suitable for non-HTTP protocols like databases or SMTP.balance roundrobindistributes requests in turn. If your backends have very different processing times,balance leastconnis a better fit — it sends each new connection to the backend with the fewest active connections.- The
checkinserver web1 10.0.0.11:80 checkenables health checking; HAProxy periodically probes the backend, removes it on failure, and adds it back once it recovers. option httpchk GET /upgrades the health check from a plain TCP probe to an HTTP probe, sending aGETrequest to/and considering the backend healthy only on a 2xx/3xx response. In production, point it at a dedicated health-check path (such as/healthz).
Enable the Stats Page
Section titled “Enable the Stats Page”HAProxy ships with a live stats page that shows the state, traffic, and session count of each backend. Add a listen section (or place it inside a frontend):
listen stats bind *:8404 stats enable stats uri /haproxy?stats stats auth admin:password stats refresh 10sAfter saving, visit http://<load-balancer-IP>:8404/haproxy?stats and log in with the username admin and password password.
Validate the Configuration
Section titled “Validate the Configuration”After every change, validate the syntax before restarting so you do not break the service:
$ sudo haproxy -c -f /etc/haproxy/haproxy.cfgConfiguration file is validIf there is an error, the output points directly to the offending line number and the reason; fix it as instructed.
SELinux Configuration
Section titled “SELinux Configuration”EL systems enable SELinux by default. HAProxy runs in the haproxy_t domain and is, by default, only allowed to connect to a set of common ports. If your backends use a non-standard port, the connection is denied by SELinux, showing up as backends that stay down even though the network itself is fine.
Turn on the boolean to allow HAProxy to connect to any port:
$ sudo setsebool -P haproxy_connect_any onOpen the Firewall
Section titled “Open the Firewall”Open the HTTP/HTTPS ports so clients can reach the load balancer:
$ sudo firewall-cmd --permanent --add-service=http$ sudo firewall-cmd --permanent --add-service=https$ sudo firewall-cmd --reloadIf the stats page uses 8404, open that port too:
$ sudo firewall-cmd --permanent --add-port=8404/tcp$ sudo firewall-cmd --reloadStart the Service
Section titled “Start the Service”$ sudo systemctl enable --now haproxyCheck the status:
$ sudo systemctl status haproxyFrom then on, after each configuration change, validate with haproxy -c -f first, then run sudo systemctl reload haproxy for a graceful reload (reload does not drop existing connections).
TLS Termination and HTTPS Redirection
Section titled “TLS Termination and HTTPS Redirection”Let HAProxy terminate TLS directly (decrypt at the load balancer, then forward in plaintext to the backends), so clients establish an encrypted connection only with HAProxy.
-
Prepare the certificate. HAProxy requires the full certificate chain and private key combined into a single
.pemfile (in the order: certificate + intermediates + private key):Combine fullchain and private key $ sudo mkdir -p /etc/haproxy/certs$ sudo bash -c 'cat fullchain.pem privkey.pem > /etc/haproxy/certs/site.pem'$ sudo chmod 600 /etc/haproxy/certs/site.pem -
Enable TLS on the
bindline, and redirect port 80 requests to HTTPS:/etc/haproxy/haproxy.cfg frontend web_frontbind *:80bind *:443 ssl crt /etc/haproxy/certs/site.pem# Redirect all plaintext access to HTTPS with a 301redirect scheme https code 301 if !{ ssl_fc }default_backend web_serversssl_fctests whether the current connection used TLS;!{ ssl_fc }means “plaintext request”, andredirect scheme httpsis applied to it. -
Validate and reload:
Validate then reload $ sudo haproxy -c -f /etc/haproxy/haproxy.cfg$ sudo systemctl reload haproxy
Make HAProxy Itself Highly Available
Section titled “Make HAProxy Itself Highly Available”A single HAProxy is still a single point of failure. In production, you typically deploy two HAProxy nodes and use Keepalived to float a virtual IP (VIP) between them: clients only ever reach the VIP, and when the primary node fails, the VIP automatically moves to the standby, giving the load balancer itself redundancy.
For the detailed Keepalived + VIP configuration, health-check scripts, and split-brain handling, see High Availability Basics.
Common Issues
Section titled “Common Issues”503 Service Unavailable
Section titled “503 Service Unavailable”This is the most common error, and it almost always means the backend has no available server: every server is down, or all health checks are failing. To investigate:
$ echo "show servers state" | sudo socat stdio /var/run/haproxy.sockThis command relies on the stats socket configured in the global section above; socat is in EPEL — install it with sudo dnf install socat. A more visual approach is to open the stats page and see which servers are red (DOWN). Common causes: the backend service is not running, a firewall blocks the health check, the path in option httpchk returns a non-2xx/3xx status, or the backend uses a non-standard port that SELinux blocks (see haproxy_connect_any above).
Binding 443 Fails / Backend Connection Refused
Section titled “Binding 443 Fails / Backend Connection Refused”- Cannot bind 443: First confirm the certificate file exists and contains the private key —
haproxy -c -freportsunable to load SSL certificateif not. Then confirm it is readable. If the log shows permission denied, it is usually an SELinux context issue; fix it withsudo restorecon -Rv /etc/haproxy/. - Backend connection refused: When the backend uses a non-standard port, be sure to run
sudo setsebool -P haproxy_connect_any on, otherwise SELinux silently refuses the connection.
The Stats Page Will Not Open
Section titled “The Stats Page Will Not Open”Check in order: whether the listen stats section includes stats enable; whether the firewall has opened the relevant port (such as 8404/tcp); whether the URL you visit exactly matches stats uri (here, /haproxy?stats); and whether the username and password match stats auth.
Locating Configuration Syntax Errors
Section titled “Locating Configuration Syntax Errors”When startup fails, run this first:
$ sudo haproxy -c -f /etc/haproxy/haproxy.cfgThe output identifies the offending line number and reason. You can also check the journal for details on the startup failure:
$ sudo journalctl -u haproxy -n 50 --no-pager