How to Configure HAProxy for HTTP and TCP Load Balancing on RHEL 7
HAProxy is one of the most widely deployed open-source load balancers in production infrastructure, renowned for its high throughput, low latency, and fine-grained configurability. On RHEL 7 it is available directly from the base repositories and integrates cleanly with systemctl and firewalld. Whether you need to distribute HTTP traffic across a cluster of web servers, balance raw TCP connections to a database tier, terminate TLS at the edge, or expose a real-time stats dashboard, HAProxy handles all of these use cases through a single unified configuration file. This guide walks you through installation, the structure of haproxy.cfg, ACL-based routing, balance algorithms, health checks, the stats page, SSL termination, and logging.
Prerequisites
- RHEL 7 with root or sudo access
- At least two backend servers (real or virtual) to load balance — referred to as
web1andweb2in examples - Firewalld running (or iptables if you prefer)
- For SSL termination: a PEM-format certificate file combining the certificate, chain, and private key
- Basic understanding of TCP/IP and HTTP
Step 1: Install HAProxy with yum
sudo yum install -y haproxy
haproxy -v
# HA-Proxy version 1.5.18 ...
RHEL 7 ships HAProxy 1.5. If you need 2.x features (multithreading, stick-tables with expire keyword improvements), enable RHSCL or install from a third-party repository such as IUS. For most production HTTP and TCP balancing workloads, 1.5 is fully capable.
Enable and start the service:
sudo systemctl enable haproxy
sudo systemctl start haproxy
sudo systemctl status haproxy
Step 2: Understanding the haproxy.cfg Structure
The configuration file at /etc/haproxy/haproxy.cfg is divided into four sections:
- global — process-level settings: user, group, log sockets, max connections, SSL defaults
- defaults — default values inherited by all frontend and backend sections unless overridden
- frontend — a listening socket that accepts client connections and dispatches them to backends
- backend — a pool of servers that receive forwarded connections
A listen section is shorthand that merges a frontend and backend into one block, useful for simple TCP proxying.
Step 3: Write a Complete HTTP Load-Balancing Configuration
Back up the default file and write a fresh configuration:
sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.bak
sudo vi /etc/haproxy/haproxy.cfg
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
log /dev/log local0 info
log /dev/log local1 notice
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 50000
user haproxy
group haproxy
daemon
# SSL/TLS defaults
ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
tune.ssl.default-dh-param 2048
#---------------------------------------------------------------------
# Default settings inherited by all frontend/backend sections
#---------------------------------------------------------------------
defaults
mode http
log global
option httplog
option dontlognull
option forwardfor
option http-server-close
option redispatch
retries 3
timeout connect 5s
timeout client 50s
timeout server 50s
timeout http-request 15s
timeout http-keep-alive 15s
timeout queue 30s
timeout tunnel 3600s
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
#---------------------------------------------------------------------
# HTTP frontend — listens on port 80, routes via ACLs
#---------------------------------------------------------------------
frontend http_frontend
bind *:80
mode http
# Capture the Host header for logging
http-request capture req.hdr(Host) len 64
# ACL definitions
acl is_api path_beg /api/
acl is_static path_end .css .js .png .jpg .jpeg .gif .ico .woff2 .svg
acl is_www hdr(host) -i www.example.com example.com
# Route API requests to a dedicated API backend
use_backend api_servers if is_api
# Route static assets to the static backend
use_backend static_servers if is_static
# Default: send to the main web backend
default_backend web_servers
#---------------------------------------------------------------------
# Main web backend — round-robin with health checks
#---------------------------------------------------------------------
backend web_servers
mode http
balance roundrobin
option httpchk GET /health HTTP/1.1rnHost: example.com
server web1 192.168.1.10:80 check inter 5s rise 2 fall 3 weight 1
server web2 192.168.1.11:80 check inter 5s rise 2 fall 3 weight 1
server web3 192.168.1.12:80 check inter 5s rise 2 fall 3 weight 2 backup
#---------------------------------------------------------------------
# API backend — least-connections for long-lived requests
#---------------------------------------------------------------------
backend api_servers
mode http
balance leastconn
option httpchk GET /api/health HTTP/1.1rnHost: api.example.com
http-request set-header X-Forwarded-Proto http
server api1 192.168.1.20:8080 check inter 3s rise 2 fall 2
server api2 192.168.1.21:8080 check inter 3s rise 2 fall 2
#---------------------------------------------------------------------
# Static asset backend — source (IP-based sticky) for CDN edge nodes
#---------------------------------------------------------------------
backend static_servers
mode http
balance source
option httpchk HEAD /favicon.ico HTTP/1.1rnHost: static.example.com
server static1 192.168.1.30:80 check
server static2 192.168.1.31:80 check
Step 4: Balance Algorithms Explained
- roundrobin — distributes requests evenly in turn; respects server weights; best general-purpose choice
- leastconn — sends each new request to the server with the fewest active connections; ideal for long-lived connections (WebSockets, database proxying)
- source — hashes the client IP to always route a given client to the same server (sticky sessions without cookies)
- uri — hashes the request URI; useful for caching tiers so the same URL always hits the same cache node
- first — fills servers sequentially; the second server only receives traffic when the first is at max connections
Step 5: Health Checks
The check keyword on each server line enables health checking. The options used above:
- inter 5s — check interval
- rise 2 — number of consecutive successful checks before marking a server UP
- fall 3 — number of consecutive failed checks before marking it DOWN
- weight 2 — server receives twice as many requests as weight-1 servers
- backup — only used when all non-backup servers are down
For HTTP health checks, option httpchk sends a real HTTP request instead of just testing the TCP connection, which catches application-level failures:
option httpchk GET /health HTTP/1.1rnHost: example.com
http-check expect status 200
Step 6: Enable the Stats Page
HAProxy includes a built-in statistics dashboard. Add a dedicated frontend or use a listen block:
listen stats
bind *:9000
mode http
stats enable
stats uri /haproxy-stats
stats realm "HAProxy Statistics"
stats auth admin:SecurePassword123
stats refresh 5s
stats show-legends
stats show-node
# Restrict access to management networks only
acl mgmt src 192.168.1.0/24 10.0.0.0/8
http-request deny if !mgmt
Open port 9000 in firewalld:
sudo firewall-cmd --permanent --add-port=9000/tcp
sudo firewall-cmd --reload
Browse to http://<server-ip>:9000/haproxy-stats to see real-time server health, connection counts, and data rates.
Step 7: SSL Termination
Create a combined PEM file from your certificate, chain, and private key:
cat /etc/ssl/certs/example.com.crt
/etc/ssl/certs/example.com-chain.crt
/etc/ssl/private/example.com.key
> /etc/haproxy/certs/example.com.pem
chmod 600 /etc/haproxy/certs/example.com.pem
chown haproxy:haproxy /etc/haproxy/certs/example.com.pem
Add an HTTPS frontend to your configuration:
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1
mode http
http-request set-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Port 443
# Redirect HTTP to HTTPS
redirect scheme https if !{ ssl_fc }
default_backend web_servers
frontend http_redirect
bind *:80
mode http
redirect scheme https code 301
Step 8: TCP Load Balancing
For raw TCP proxying (MySQL, PostgreSQL, Redis), use mode tcp and a listen block:
listen mysql_cluster
bind *:3306
mode tcp
balance leastconn
option tcp-check
timeout connect 3s
timeout client 1m
timeout server 1m
server db1 192.168.1.40:3306 check
server db2 192.168.1.41:3306 check
Step 9: Configure rsyslog Logging
HAProxy logs to syslog. On RHEL 7 with rsyslog, create a dedicated configuration:
sudo vi /etc/rsyslog.d/haproxy.conf
$ModLoad imudp
$UDPServerRun 514
$UDPServerAddress 127.0.0.1
local0.* /var/log/haproxy.log
local1.* /var/log/haproxy-notice.log
sudo systemctl restart rsyslog
Step 10: Validate and Reload
# Syntax check
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# Reload without dropping active connections
sudo systemctl reload haproxy
# Tail the log
sudo tail -f /var/log/haproxy.log
Conclusion
HAProxy on RHEL 7 delivers enterprise-grade load balancing through a straightforward configuration language. The global/defaults/frontend/backend structure keeps your configuration modular and readable as it grows. ACL-based routing lets you direct different request types — API calls, static assets, application pages — to purpose-built backend pools, each with its own health-check strategy and balance algorithm. The built-in stats page provides instant visibility into backend health without external tooling, and the systemd reload mechanism ensures configuration changes take effect with zero connection drops. For production deployments, combine HAProxy with keepalived on two nodes for a highly available, failover-capable load balancing layer.