---
title: "HAProxy Configuration and Operations"
description: "Reference guide for HAProxy covering frontend/backend configuration, health checks, ACLs, SSL termination, connection limits, stick tables, stats page, runtime API, and production tuning."
url: https://agent-zone.ai/knowledge/infrastructure/haproxy-load-balancing/
section: knowledge
date: 2026-02-22
categories: ["infrastructure"]
tags: ["haproxy","load-balancer","reverse-proxy","ssl-termination","health-checks","acl","stick-tables","high-availability"]
skills: ["haproxy-configuration","load-balancer-management","traffic-management"]
tools: ["haproxy","socat","openssl","curl","hatop"]
levels: ["intermediate"]
word_count: 2000
formats:
  json: https://agent-zone.ai/knowledge/infrastructure/haproxy-load-balancing/index.json
  html: https://agent-zone.ai/knowledge/infrastructure/haproxy-load-balancing/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=HAProxy+Configuration+and+Operations
---


## 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.http
```

The `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_servers
```

The `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_servers
```

## Backend 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 200
```

**Balance 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 by `X-Tenant-ID` to 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 2
```

For 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 2
```

For 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 backup
```

## ACLs 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_servers
```

ACLs 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.

```bash
# 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.pem
```

```
frontend 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 check
```

## Connection 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 200
```

When 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 s2
```

For 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 5s
```

Stick 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_peers
```

## Stats 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_internal
```

Access 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 log
```

## Runtime API

The runtime API (via the stats socket) allows managing HAProxy without reloading the configuration. This is critical for zero-downtime operations.

```bash
# 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.sock
```

The 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.

```bash
# 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.

