Skip to main content

HAProxy

HAProxy (High Availability Proxy) is a free, open-source TCP/HTTP load balancer and proxy server. This guide covers installation, basic configuration, and common usage patterns.

Prerequisites

RequirementDetails
OSLinux (Ubuntu, Debian, RHEL, CentOS, Fedora)
Privilegesroot or sudo access
PortsTypically 80, 443, or custom ports — must be free
SSL certsRequired only for HTTPS termination

Installation

Ubuntu / Debian

sudo apt update
sudo apt install -y haproxy

To install a newer version via the official PPA:

# Create a directory for the key if it doesn't exist
sudo install -d -m 0755 /usr/share/keyrings
# Download the GPG key
sudo wget -qO /usr/share/keyrings/HAPROXY-key-community.asc https://pks.haproxy.com/linux/community/RPM-GPG-KEY-HAProxy

# Add the HAProxy repository for noble and HAProxy 3.2
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/HAPROXY-key-community.asc] https://www.haproxy.com/download/haproxy/performance/ubuntu/ha32 noble main" | sudo tee /etc/apt/sources.list.d/haproxy.list

sudo apt-get update
sudo apt-get install haproxy-awslc

RHEL / CentOS / Fedora

# CentOS / RHEL
sudo yum install -y haproxy

# Fedora
sudo dnf install -y haproxy

From Source

# Install build dependencies
sudo apt install -y build-essential libssl-dev libpcre3-dev zlib1g-dev

# Download and compile
wget https://www.haproxy.org/download/2.9/src/haproxy-2.9.0.tar.gz
tar -xzf haproxy-2.9.0.tar.gz
cd haproxy-2.9.0

make TARGET=linux-glibc USE_OPENSSL=1 USE_ZLIB=1 USE_PCRE=1
sudo make install

Docker

docker run -d \
--name haproxy \
-p 80:80 \
-v $(pwd)/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro \
haproxy:latest

To reload config inside Docker:

docker kill -s HUP haproxy

Configuration Overview

Config File Location

PlatformDefault Path
Ubuntu / Debian/etc/haproxy/haproxy.cfg
RHEL / CentOS/etc/haproxy/haproxy.cfg
Source install/usr/local/etc/haproxy/haproxy.cfg
DockerMounted via volume

Config Structure

global
# Process-level settings (logging, ulimits, SSL)

defaults
# Default values inherited by frontends and backends

frontend <name>
# What to listen on and how to route traffic

backend <name>
# Where to send traffic (servers, health checks, algorithms)

listen <name>
# Shorthand: combines frontend + backend in one block

Core Sections Explained

global

Controls the HAProxy process itself.

global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon

# SSL tuning
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults

Default values applied to all frontends and backends unless overridden.

defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5s
timeout client 50s
timeout server 50s
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http

frontend

Defines where HAProxy listens and how it directs traffic.

frontend http_front
bind *:80
default_backend http_back

# Optional: redirect HTTP → HTTPS
# redirect scheme https if !{ ssl_fc }

backend

Defines the pool of servers that handle requests.

backend http_back
balance roundrobin
server web1 192.168.1.10:8080 check
server web2 192.168.1.11:8080 check
server web3 192.168.1.12:8080 check backup

listen

Combines frontend and backend in a single block — convenient for simple setups.

listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s

Common Configuration Examples

HTTP Load Balancer

frontend http_front
bind *:80
default_backend web_servers

backend web_servers
balance roundrobin
option httpchk GET /health
server web1 10.0.0.1:80 check
server web2 10.0.0.2:80 check
server web3 10.0.0.3:80 check

HTTPS Termination (SSL/TLS)

Combine your certificate and private key into a single .pem file:

cat fullchain.pem privkey.pem > /etc/haproxy/certs/example.com.pem
frontend https_front
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
http-request set-header X-Forwarded-Proto https
default_backend web_servers

frontend http_front
bind *:80
redirect scheme https code 301

TCP Proxy

Use mode tcp for non-HTTP protocols (databases, SMTP, custom TCP services).

frontend mysql_front
bind *:3306
mode tcp
default_backend mysql_servers

backend mysql_servers
mode tcp
balance leastconn
server db1 10.0.0.10:3306 check
server db2 10.0.0.11:3306 check

Health Checks

backend web_servers
option httpchk GET /healthz HTTP/1.1\r\nHost:\ example.com
http-check expect status 200

# inter: interval, fall: failures before down, rise: successes before up
server web1 10.0.0.1:80 check inter 5s fall 3 rise 2
server web2 10.0.0.2:80 check inter 5s fall 3 rise 2

Sticky Sessions

Route the same client to the same backend server using cookies.

backend web_servers
balance roundrobin
cookie SERVERID insert indirect nocache
server web1 10.0.0.1:80 check cookie web1
server web2 10.0.0.2:80 check cookie web2

Load Balancing Algorithms

AlgorithmKeywordBest For
Round RobinroundrobinEqually-spec'd servers, short requests
Least ConnectionsleastconnLong-lived connections, varying load
Source IP HashsourceSticky routing without cookies
URI HashuriCache servers (same URI → same server)
RandomrandomLarge, homogeneous server pools
Static Round Robinstatic-rrRound robin but not dynamic weight changes
backend web_servers
balance leastconn
# ...

ACLs and Routing

ACLs (Access Control Lists) let you route traffic based on request properties.

frontend http_front
bind *:80

# Define ACLs
acl is_api path_beg /api/
acl is_static path_end .css .js .png .jpg .ico
acl is_mobile hdr_sub(User-Agent) -i mobile android iphone

# Route based on ACLs
use_backend api_servers if is_api
use_backend static_servers if is_static
use_backend mobile_servers if is_mobile
default_backend web_servers

Common ACL Matchers

MatcherExampleMatches
path_begpath_beg /apiURL starts with /api
path_endpath_end .phpURL ends with .php
path_regpath_reg ^/v[0-9]+/URL matches regex
hdrhdr(host) example.comExact header value
hdr_beghdr_beg(host) api.Header starts with
hdr_subhdr_sub(User-Agent) curlHeader contains string
srcsrc 10.0.0.0/8Source IP range

Stats Dashboard

Enable the built-in web UI for real-time monitoring.

listen stats
bind *:8404
stats enable
stats uri /stats
stats realm HAProxy\ Stats
stats auth admin:yourpassword
stats refresh 5s
stats show-legends
stats show-node

Access at: http://your-server:8404/stats

⚠️ Restrict access to the stats page in production using firewall rules or ACLs.


Managing the Service

# Start / Stop / Restart
sudo systemctl start haproxy
sudo systemctl stop haproxy
sudo systemctl restart haproxy

# Graceful reload (no dropped connections)
sudo systemctl reload haproxy

# Enable on boot
sudo systemctl enable haproxy

# Check status
sudo systemctl status haproxy

Runtime API (via socket)

# Connect to the runtime API
sudo socat stdio /run/haproxy/admin.sock

# Useful commands
echo "show info" | sudo socat stdio /run/haproxy/admin.sock
echo "show servers state" | sudo socat stdio /run/haproxy/admin.sock
echo "show stat" | sudo socat stdio /run/haproxy/admin.sock

# Disable a server temporarily
echo "disable server web_servers/web1" | sudo socat stdio /run/haproxy/admin.sock

# Re-enable
echo "enable server web_servers/web1" | sudo socat stdio /run/haproxy/admin.sock

Logging

HAProxy logs to syslog by default. Configure rsyslog to capture its output:

# /etc/rsyslog.d/49-haproxy.conf
$AddUnixListenSocket /dev/log

:programname, startswith, "haproxy" {
/var/log/haproxy.log
stop
}
sudo systemctl restart rsyslog

To view logs:

sudo tail -f /var/log/haproxy.log

Testing Your Config

Always validate before reloading in production.

# Check config syntax
sudo haproxy -c -f /etc/haproxy/haproxy.cfg

# Check with a specific config file path
haproxy -c -f /path/to/custom.cfg

# Dry-run (parse and exit without binding ports)
sudo haproxy -f /etc/haproxy/haproxy.cfg -c

Expected output on success:

Configuration file is valid

Troubleshooting

SymptomLikely CauseFix
502 Bad GatewayBackend servers downCheck server health, firewall rules
503 Service UnavailableAll backends failed health checksVerify health check endpoint and ports
Config fails to reloadSyntax errorRun haproxy -c -f haproxy.cfg and fix errors
Port already in useAnother process on same portsudo ss -tlnp | grep <port>
SSL handshake failureWrong cert/key or TLS versionCheck cert path, key match, and ssl-min-ver
High memory usagemaxconn too highTune maxconn in global and per-server
# Check what's listening on a port
sudo ss -tlnp | grep 80

# View HAProxy version and build options
haproxy -v

# Test backend connectivity from the HAProxy host
curl -I http://10.0.0.1:8080/health

Security Hardening Tips

  • Run as non-root: Use user haproxy and group haproxy in the global section.
  • Use chroot: Restrict filesystem access with chroot /var/lib/haproxy.
  • Enforce TLS 1.2+: Set ssl-min-ver TLSv1.2 to disable older protocols.
  • Hide version info: Add http-response del-header Server to remove server headers.
  • Rate limiting: Use stick-table to throttle abusive clients:
frontend http_front
bind *:80
stick-table type ip size 100k expire 30s store conn_cur,conn_rate(10s),http_req_rate(10s)
tcp-request connection track-sc0 src
tcp-request connection reject if { sc_conn_rate(0) gt 100 }
default_backend web_servers
  • Restrict stats page: Limit access by IP with an ACL:
listen stats
bind *:8404
acl allowed_ips src 10.0.0.0/8 127.0.0.1
http-request deny if !allowed_ips
stats enable
stats uri /stats