DevOpsil
Nginx
91%
Needs Review

HAProxy Configuration Guide: TCP/HTTP Load Balancing, Health Checks, and SSL

Muhammad HassanMuhammad Hassan6 min read

HAProxy's Configuration Model

HAProxy's config is split into four sections:

  • global — process-level settings (logging, limits, socket)
  • defaults — defaults inherited by all proxies
  • frontend — listens for incoming connections, routes to backends
  • backend — pool of servers + health check config
  • listen — combines frontend + backend in one block (used for simple TCP)

Understanding this model makes the config readable and predictable.


Installation

# RHEL/Rocky/AlmaLinux
sudo dnf install haproxy -y

# Ubuntu/Debian
sudo apt install haproxy -y

# Check version
haproxy -v
sudo systemctl enable haproxy

Base Configuration

/etc/haproxy/haproxy.cfg:

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

    # TLS 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
    log global
    mode http
    option httplog
    option dontlognull
    option forwardfor
    option http-server-close
    timeout connect  5s
    timeout client   50s
    timeout server   50s
    timeout http-request 10s
    timeout http-keep-alive 2s
    timeout queue    30s
    retries 3
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 503 /etc/haproxy/errors/503.http

Frontend Configuration

frontend http_in
    bind *:80

    # Redirect all HTTP to HTTPS
    redirect scheme https code 301

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1

    # Security headers
    http-response set-header Strict-Transport-Security "max-age=63072000"
    http-response set-header X-Content-Type-Options "nosniff"
    http-response set-header X-Frame-Options "SAMEORIGIN"

    # Route by hostname
    use_backend api_servers  if { hdr(host) -i api.example.com }
    use_backend static_cdn   if { path_beg /static/ /assets/ /images/ }
    default_backend web_servers

Backend Configuration

backend web_servers
    balance roundrobin
    option httpchk GET /health HTTP/1.1\r\nHost:\ example.com

    server web01 10.0.1.10:8080 check inter 5s fall 3 rise 2 weight 1
    server web02 10.0.1.11:8080 check inter 5s fall 3 rise 2 weight 1
    server web03 10.0.1.12:8080 check inter 5s fall 3 rise 2 weight 2
    server web04 10.0.1.13:8080 check inter 5s fall 3 rise 2 backup

Server directive flags:

  • check — enable health checking
  • inter 5s — check every 5 seconds
  • fall 3 — mark down after 3 failures
  • rise 2 — mark up after 2 successes
  • weight 2 — receives 2× traffic in round-robin
  • backup — only used when all primary servers are down

Balance Algorithms

AlgorithmConfigBest For
Round-robinbalance roundrobinStateless, equal-capacity servers
Least connectionsbalance leastconnLong-lived connections (DB, WebSockets)
Source IP hashbalance sourceStateful apps needing soft affinity
URI hashbalance uriCache servers (same URI → same server)
Randombalance randomLarge clusters, simple distribution
backend db_servers
    balance leastconn   # route new connections to least-busy server
    ...

backend cache_servers
    balance uri         # same URL always hits same cache server
    hash-type consistent
    ...

ACLs and Routing

HAProxy ACLs allow conditional routing based on any request attribute:

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem

    # Define ACLs
    acl is_api      hdr(host) -i api.example.com
    acl is_websocket hdr(Upgrade) -i websocket
    acl is_static   path_beg /static/ /assets/
    acl is_post     method POST
    acl is_large    req.body_len gt 10000000   # > 10MB

    # Apply routing
    use_backend ws_servers       if is_websocket
    use_backend static_servers   if is_static
    use_backend api_servers      if is_api
    default_backend web_servers

Rate Limiting

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem

    # Stick table: track IPs, store connection rate over 30s
    stick-table type ip size 200k expire 60s store conn_cur,conn_rate(30s),http_req_rate(10s)

    # Track source IP
    http-request track-sc0 src

    # Deny if > 200 req/10s or > 50 concurrent connections
    http-request deny deny_status 429 if { sc_http_req_rate(0) gt 200 }
    http-request deny deny_status 429 if { sc_conn_cur(0) gt 50 }

    default_backend web_servers

WebSocket Support

frontend ws_front
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    option http-server-close
    option forwardfor

    # Detect WebSocket upgrade
    acl is_websocket hdr(Upgrade) -i websocket
    use_backend ws_servers if is_websocket
    default_backend web_servers

backend ws_servers
    balance leastconn     # leastconn is better for long-lived WS connections
    option http-server-close
    timeout tunnel 1h     # keep tunnel open for long WebSocket sessions

    server ws01 10.0.1.10:3000 check
    server ws02 10.0.1.11:3000 check

TCP Load Balancing (Layer 4)

For non-HTTP protocols (MySQL, PostgreSQL, Redis, raw TCP):

listen mysql_cluster
    bind *:3306
    mode tcp
    balance leastconn
    option tcp-check

    server db01 10.0.1.20:3306 check
    server db02 10.0.1.21:3306 check backup

listen redis
    bind *:6379
    mode tcp
    balance first      # always route to first available server
    option tcp-check

    server redis01 10.0.1.30:6379 check
    server redis02 10.0.1.31:6379 check backup

Stats Dashboard

frontend stats
    bind *:8404
    mode http
    stats enable
    stats uri /stats
    stats refresh 10s
    stats auth admin:securepassword
    stats admin if TRUE           # allow enable/disable servers from UI
    stats hide-version
    stats show-legends
    stats show-node

Access at http://your-lb:8404/stats. The dashboard shows:

  • Request rates, error rates per frontend/backend
  • Active/down server counts
  • Queue depths
  • Session counts

Runtime Socket API

Manage HAProxy without restarts:

# Show all server states
echo "show servers state" | sudo socat stdio /run/haproxy/admin.sock

# Disable a server gracefully (drain existing connections)
echo "set server web_servers/web03 state drain" | sudo socat stdio /run/haproxy/admin.sock

# Put server in maintenance (reject new and drop existing)
echo "set server web_servers/web03 state maint" | sudo socat stdio /run/haproxy/admin.sock

# Re-enable server
echo "set server web_servers/web03 state ready" | sudo socat stdio /run/haproxy/admin.sock

# Change server weight at runtime
echo "set server web_servers/web01 weight 2" | sudo socat stdio /run/haproxy/admin.sock

# Show current traffic stats (CSV)
echo "show stat" | sudo socat stdio /run/haproxy/admin.sock | cut -d, -f1,2,18,19,48

# Reload config without dropping connections (HAProxy 2.0+)
sudo haproxy -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid)

Logging

Configure HAProxy to log to rsyslog:

# /etc/rsyslog.d/49-haproxy.conf
local0.*    /var/log/haproxy.log
local1.*    /var/log/haproxy.log

# Prevent duplicates in syslog
& stop
sudo systemctl restart rsyslog

Custom log format for structured logging:

defaults
    log-format '{"ts":"%t","client":"%ci:%cp","frontend":"%ft","backend":"%b/%s","status":%ST,"req_time":%Tr,"conn_time":%Tc,"bytes":%B,"request":"%r"}'

Quick Reference

# Validate config
sudo haproxy -c -f /etc/haproxy/haproxy.cfg

# Reload (hot reload, no dropped connections)
sudo systemctl reload haproxy

# Check active connections
echo "show info" | sudo socat stdio /run/haproxy/admin.sock | grep CurrConns

# Top backends by request rate
echo "show stat" | sudo socat stdio /run/haproxy/admin.sock | awk -F, 'NR>1 {print $1,$2,$48}' | sort -k3 -rn | head
Share:

Was this article helpful?

Muhammad Hassan
Muhammad Hassan

Network & Traffic Engineer

Packets don't lie. I design and troubleshoot the network layer that everything else depends on — Nginx, Envoy, HAProxy, DNS, CDNs, and everything in between. If it touches a socket, it's my problem.

Related Articles

More in Nginx

View all →

Discussion