Configuration File Structure#
HAProxy uses a single configuration file, typically /etc/haproxy/haproxy.cfg. The configuration is divided into four sections: global (process-level settings), defaults (default values for all proxies), frontend (client-facing listeners), and backend (server pools).
global
log /dev/log local0
log /dev/log local1 notice
maxconn 50000
user haproxy
group haproxy
daemon
stats socket /run/haproxy/admin.sock mode 660 level admin
tune.ssl.default-dh-param 2048
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5s
timeout client 30s
timeout server 30s
timeout http-request 10s
timeout http-keep-alive 10s
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.httpThe maxconn in the global section sets the hard limit on total simultaneous connections. The timeout values in defaults apply to all frontends and backends unless overridden. The stats socket directive enables the runtime API, which is essential for operational management.
Frontend Configuration#
A frontend defines how HAProxy accepts client connections. It binds to an address and port, applies rules to incoming requests, and routes them to backends.
frontend http_front
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/combined.pem alpn h2,http/1.1
mode http
# Redirect HTTP to HTTPS
http-request redirect scheme https unless { ssl_fc }
# Route based on Host header
acl is_api hdr(host) -i api.example.com
acl is_app hdr(host) -i app.example.com
acl is_admin hdr(host) -i admin.example.com
use_backend api_servers if is_api
use_backend app_servers if is_app
use_backend admin_servers if is_admin
default_backend app_serversThe bind directive accepts connections. The ssl crt parameter specifies a PEM file containing the certificate and private key concatenated together. The alpn h2,http/1.1 enables HTTP/2 negotiation. Multiple bind directives allow listening on multiple ports in the same frontend.
For TCP mode (L4), use mode tcp and omit HTTP-specific directives:
frontend mysql_front
bind *:3306
mode tcp
option tcplog
default_backend mysql_serversBackend Configuration#
A backend defines a pool of servers that handle requests. Each server has an address, port, and optional parameters controlling its behavior.
backend api_servers
mode http
balance roundrobin
option httpchk GET /health
http-check expect status 200
server api1 10.0.1.10:8080 check inter 10s fall 3 rise 2 maxconn 200
server api2 10.0.1.11:8080 check inter 10s fall 3 rise 2 maxconn 200
server api3 10.0.1.12:8080 check inter 10s fall 3 rise 2 maxconn 200Balance algorithms:
roundrobin– Servers are used in rotation. Supports server weights. This is the default and works well when request processing times are uniform.leastconn– New connections go to the server with the fewest active connections. Better than roundrobin when request durations vary significantly (some requests take 10ms, others take 5 seconds).source– Hash of the client source IP determines the server. Provides session affinity without cookies but breaks when clients share IPs (NAT).uri– Hash of the request URI determines the server. Useful for caching – the same URL always hits the same backend, maximizing cache hit rates.hdr(name)– Hash of a request header value. Route byX-Tenant-IDto isolate tenant traffic to specific backends.
Server parameters:
check– Enable health checking for this server.inter 10s– Health check interval (10 seconds between checks).fall 3– Mark the server as down after 3 consecutive failed checks.rise 2– Mark the server as up after 2 consecutive successful checks.maxconn 200– Maximum concurrent connections to this server. Excess connections are queued.weight 100– Relative weight for load balancing (default 100, range 0-256).backup– Use this server only when all non-backup servers are down.
Health Checks#
HAProxy supports multiple health check types. HTTP checks are the most common for web services.
backend app_servers
mode http
option httpchk
http-check send meth GET uri /health ver HTTP/1.1 hdr Host app.example.com
http-check expect status 200
# Alternative: check response body
http-check expect string "status":"healthy"
server app1 10.0.1.10:8080 check inter 5s fall 3 rise 2
server app2 10.0.1.11:8080 check inter 5s fall 3 rise 2For TCP services, HAProxy performs a basic TCP connection check by default when check is specified. For services that need more validation:
backend redis_servers
mode tcp
option tcp-check
tcp-check send PING\r\n
tcp-check expect string +PONG
server redis1 10.0.1.20:6379 check inter 5s fall 3 rise 2For MySQL/PostgreSQL, HAProxy can use protocol-specific checks:
backend mysql_servers
mode tcp
option mysql-check user haproxy_check
server db1 10.0.1.30:3306 check inter 10s fall 3 rise 2
server db2 10.0.1.31:3306 check inter 10s fall 3 rise 2 backupACLs and Routing#
Access Control Lists (ACLs) define conditions for routing decisions. ACLs can match on any part of the request: headers, path, source IP, SSL properties, and more.
frontend http_front
bind *:443 ssl crt /etc/haproxy/certs/combined.pem
# Path-based routing
acl is_api path_beg /api/
acl is_static path_beg /static/ /assets/ /images/
acl is_websocket hdr(Upgrade) -i websocket
# Header-based routing
acl is_mobile hdr_sub(User-Agent) -i mobile android iphone
acl has_auth_token hdr(Authorization) -m found
# Source IP filtering
acl is_internal src 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
acl is_blocked src -f /etc/haproxy/blocked-ips.txt
# Request method filtering
acl is_options method OPTIONS
# Block bad actors
http-request deny if is_blocked
# CORS preflight handling
http-request return status 204 hdr Access-Control-Allow-Origin "*" if is_options
# Route to backends
use_backend ws_servers if is_websocket
use_backend static_servers if is_static
use_backend api_servers if is_api
use_backend mobile_servers if is_mobile
default_backend app_serversACLs can be combined with boolean operators. if is_api is_internal means both conditions must be true (AND). if is_api or is_static means either condition (OR). if !is_internal negates the condition (NOT).
External ACL files (-f /etc/haproxy/blocked-ips.txt) allow managing large lists without editing the main configuration. Each line in the file contains one entry. The file is loaded at startup and on configuration reload.
SSL/TLS Termination#
HAProxy handles SSL termination at the frontend. Certificates are provided as PEM files containing the certificate chain and private key concatenated together.
# Create a combined PEM file
cat /etc/letsencrypt/live/example.com/fullchain.pem \
/etc/letsencrypt/live/example.com/privkey.pem \
> /etc/haproxy/certs/example.com.pemfrontend https_front
bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
mode http
# SSL hardening
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
# Add forwarded headers
http-request set-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Port %[dst_port]When crt points to a directory instead of a file, HAProxy loads all PEM files in that directory and uses SNI (Server Name Indication) to select the correct certificate per domain. This simplifies multi-domain setups.
For SSL passthrough (L4, forwarding encrypted traffic to backends without termination):
frontend tcp_ssl_front
bind *:443
mode tcp
option tcplog
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
acl is_api req_ssl_sni -i api.example.com
use_backend api_ssl_passthrough if is_api
default_backend app_ssl_passthrough
backend api_ssl_passthrough
mode tcp
server api1 10.0.1.10:443 checkConnection Limits and Queuing#
Protecting backends from overload is essential for stability. HAProxy provides connection limits at the frontend, backend, and individual server level.
frontend http_front
bind *:80
maxconn 10000 # Max connections this frontend accepts
backend api_servers
mode http
balance leastconn
fullconn 1000 # Expected concurrent connections at full load
timeout queue 30s # Max time a request waits in queue
option queue-timeout-compact
server api1 10.0.1.10:8080 check maxconn 200
server api2 10.0.1.11:8080 check maxconn 200When a server reaches its maxconn, new requests are queued in the backend queue. The timeout queue setting controls how long a request waits before HAProxy returns a 503 to the client. Without timeout queue, requests queue indefinitely (up to timeout client), which can lead to cascading failures as clients pile up.
Rate limiting with stick tables prevents abuse:
frontend http_front
bind *:80
# Track request rate per source IP
stick-table type ip size 100k expire 30s store http_req_rate(10s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }This tracks each source IP’s request rate over a 10-second window and denies requests from IPs exceeding 100 requests per 10 seconds.
Stick Tables#
Stick tables are in-memory key-value stores for tracking connection and request metadata. They enable rate limiting, session persistence, and abuse detection.
backend app_servers
mode http
balance roundrobin
# Cookie-based session persistence
cookie SERVERID insert indirect nocache
server app1 10.0.1.10:8080 check cookie s1
server app2 10.0.1.11:8080 check cookie s2For more advanced tracking:
frontend http_front
bind *:80
# Track multiple counters per source IP
stick-table type ip size 200k expire 5m \
store conn_cur,conn_rate(10s),http_req_rate(10s),http_err_rate(10s)
http-request track-sc0 src
# Deny if more than 50 concurrent connections from one IP
http-request deny deny_status 429 if { src_conn_cur gt 50 }
# Deny if error rate is too high (possible scanner)
http-request deny deny_status 403 if { sc_http_err_rate(0) gt 20 }
# Tarpit slow scanners (hold the connection open, wasting their resources)
http-request tarpit if { sc_http_req_rate(0) gt 200 }
timeout tarpit 5sStick tables can be synchronized between HAProxy peers for high-availability setups:
peers haproxy_peers
peer haproxy1 10.0.0.1:1024
peer haproxy2 10.0.0.2:1024
frontend http_front
stick-table type ip size 200k expire 5m store http_req_rate(10s) peers haproxy_peersStats Page#
The built-in stats page provides real-time visibility into HAProxy’s state: server health, request rates, error rates, queue depth, and session counts.
listen stats
bind *:8404
mode http
stats enable
stats uri /stats
stats refresh 10s
stats show-legends
stats admin if TRUE # Enable admin actions (drain, disable servers)
# Restrict access
acl is_internal src 10.0.0.0/8 172.16.0.0/12
http-request deny unless is_internalAccess the stats page at http://haproxy-host:8404/stats. The admin mode (stats admin if TRUE) allows enabling, disabling, and draining servers directly from the web interface. Restrict this to internal networks.
For Prometheus integration, HAProxy 2.0+ includes a built-in Prometheus exporter:
frontend prometheus
bind *:8405
mode http
http-request use-service prometheus-exporter if { path /metrics }
no logRuntime API#
The runtime API (via the stats socket) allows managing HAProxy without reloading the configuration. This is critical for zero-downtime operations.
# Check server states
echo "show servers state" | socat stdio /run/haproxy/admin.sock
# Disable a server for maintenance (stop sending new connections, drain existing)
echo "set server api_servers/api1 state drain" | socat stdio /run/haproxy/admin.sock
# Re-enable the server
echo "set server api_servers/api1 state ready" | socat stdio /run/haproxy/admin.sock
# Change server weight (shift traffic away gradually)
echo "set server api_servers/api1 weight 50" | socat stdio /run/haproxy/admin.sock
# View current stick table contents
echo "show table http_front" | socat stdio /run/haproxy/admin.sock
# Clear a specific entry from the stick table (unblock an IP)
echo "clear table http_front key 192.168.1.100" | socat stdio /run/haproxy/admin.sock
# View current session info
echo "show sess" | socat stdio /run/haproxy/admin.sock
# Show HAProxy info (uptime, connection counts, memory)
echo "show info" | socat stdio /run/haproxy/admin.sockThe runtime API is the preferred method for operational changes like draining a server before maintenance. Changes made through the runtime API are not persisted – they are lost on HAProxy restart. For permanent changes, update the configuration file and reload.
Configuration Reload#
HAProxy supports seamless configuration reloads. The new process starts, takes over the listening sockets, and the old process finishes handling existing connections before exiting.
# Validate configuration before reloading
haproxy -c -f /etc/haproxy/haproxy.cfg
# Reload via systemd
systemctl reload haproxy
# Manual reload (starts new process, gracefully stops old)
haproxy -f /etc/haproxy/haproxy.cfg -sf $(cat /run/haproxy.pid)Always validate with haproxy -c before reloading. A configuration error in the new file will prevent the new process from starting, but the old process continues serving traffic. This means a failed reload does not cause an outage, but it leaves you running an outdated configuration.
Common Gotchas#
Timeouts too short. The default timeout server 30s may be too short for backend processes that handle long-running requests (report generation, file uploads). If HAProxy closes the connection before the backend responds, the client gets a 504 and the backend wastes resources completing a request nobody will receive. Set timeout server to match your application’s maximum expected response time.
Missing option httpchk with HTTP checks. The http-check directives have no effect without option httpchk in the backend block. Without it, HAProxy performs TCP-only checks even though http-check expect is defined. This is a silent misconfiguration that makes health checks less useful.
Forgetting http-request set-header X-Forwarded-For. Unlike Nginx, HAProxy does not automatically add X-Forwarded-For in all configurations. Use option forwardfor in the defaults or backend block to add it automatically, or set it manually with http-request set-header.
Stats socket permissions. The stats socket file must be accessible to the user running management commands. The mode 660 level admin setting in the global section controls file permissions and API access level. Without level admin, many runtime API commands are rejected.