← News
/PLAYBOOK · 2026-08-31

HAProxy Security Hardening in Practice: Load Balancer Protection Guide

HAProxy is a high-performance load balancer often exposed to the internet, facing unauthorized access, config leakage, and weak TLS. This guide covers access control, TLS hardening, management-port protection, and log audit with a complete, reproducible HAProxy hardening config.

HAProxy hardening is load-balancer-layer security. HAProxy sits at the traffic edge. Stolen admin access or a config defect can mean traffic hijack, config tampering, or direct backend compromise. This guide is for operators and security staff running HAProxy in production. The config is meant to be applied as-is.

Scope

This approach fits:

Prerequisites

How It Works

Three main risk classes: exposed management plane (stats page unauthenticated and remotely reachable by default); weak transport (TLS 1.0/1.1 or RC4 and similar); missing access control (any source can hit backends). Hardening: HTTP Basic Auth on the management plane, bound to localhost or an internal address; TLS 1.2+ and a tight cipher list on the wire; ACLs on the business plane for source and behavior; logging to close the loop.

Procedure

Step 1: Generate a stats-page password hash

Hash with openssl (do not store plaintext in the config):

# Generate a SHA-256 password hash; replace admin123 with a strong password
printf "admin123" | openssl passwd -6 -salt $(openssl rand -hex 4)
# Output similar to: $6$abcd1234$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Step 2: Write the hardened main config

Edit `/etc/haproxy/haproxy.cfg`. Full example:

global
    log /dev/log local0
    maxconn 50000
    # Drop privileges to reduce root privilege-escalation risk
    user haproxy
    group haproxy
    # stats socket: local administration only
    stats socket /var/run/haproxy.sock mode 600 level admin

defaults
    log global
    mode http
    option httplog
    option dontlognull
    timeout connect 5s
    timeout client  30s
    timeout server  30s
    # Hide backend server information
    server-state-file /var/run/haproxy.state

# Management page: localhost-only access + Basic Auth
listen stats
    bind 127.0.0.1:8404
    mode http
    stats enable
    stats uri /stats
    stats refresh 10s
    stats auth admin:xxxxxx
    stats admin if LOCALHOST

# Frontend: TLS hardening + ACL restrictions
frontend web_front
    bind *:443 ssl crt /etc/haproxy/certs/site.pem
    # Enable TLS 1.2 and 1.3 only
    ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
    # Block scanner/crawler User-Agents
    acl is_bad_ua hdr_sub(User-Agent) -i sqlmap nikto nmap
    http-request deny if is_bad_ua
    # Limit concurrent connections from a single source IP
    stick-table type ip size 100k expire 30s store http_req_rate(10s)
    http-request track-sc0 src
    http-request deny if { sc_http_req_rate(0) gt 100 }
    default_backend web_back

backend web_back
    balance roundrobin
    server web1 192.168.1.10:8080 check inter 3s fall 3 rise 2
    server web2 192.168.1.11:8080 check inter 3s fall 3 rise 2

Key knobs: stats auth uses the ciphertext from Step 1; no-tlsv10/no-tlsv11 disable old protocols; stick-table plus `http_req_rate` caps each IP at 100 requests per 10 seconds.

Step 3: Validate and reload

# Syntax check; if Errors found, fix them before proceeding
haproxy -c -f /etc/haproxy/haproxy.cfg
# Graceful reload without interrupting connections
systemctl reload haproxy

Verification

# Verify TLS version: should show TLSv1.2 or TLSv1.3 only
openssl s_client -connect 127.0.0.1:443 -tls1_1 </dev/null 2>&1 | grep -i "protocol"
# Verify the stats page requires authentication
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8404/stats
# Without credentials should return 401; with credentials return 200
curl -s -u admin:password -o /dev/null -w "%{http_code}" http://127.0.0.1:8404/stats
# Verify external access to the management port is denied (run from another host)
nc -vz your-serverIP 8404

FAQ

FAQ 1: stats page unreachable from the admin workstation?

Confirm the bind address is internal or localhost (e.g. `bind 10.0.0.5:8404`). Open the port on the host firewall: `firewall-cmd --add-port=8404/tcp --permanent && firewall-cmd --reload`. Do not bind stats to 0.0.0.0 in production.

FAQ 2: TLS 1.3 breaks older clients?

Keep TLS 1.2 as a compatibility layer and set `ssl-default-bind-ciphers` to ECDHE suites: `ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256`. If TLS 1.0 is still required, assess the business risk and at least enable HSTS against downgrade.

Summary

Three lines: shrink the management plane, harden transport, tighten the business plane. Bind the admin port internally and authenticate it. Force TLS 1.2+ and drop weak ciphers. ACL plus rate tables on the front. Use `haproxy -c` then a graceful reload so traffic stays up. Put this config under change control. Re-run the verification commands after every HAProxy upgrade.