Skip to content

HAProxy peer protocol

Replicate stick tables across HAProxy instances with the peer protocol — config, network requirements, the reload handover, and the failure modes when peers fall out of sync.

The HAProxy peer protocol replicates stick-table contents across multiple HAProxy instances. Without it, a cluster of load balancers has N independent rate-limit counters, sticky session tables, and gpc counters — and any reload wipes them. With it, the state is shared, survives reloads on any one node, and survives the loss of a node. This article covers the config, the network requirements, the reload handover, and the failure modes we plan for.

How to verify

For an existing peer setup, the runtime API tells you which peers are connected and synced:

echo "show peers" | sudo socat /run/haproxy/admin.sock -
echo "show table" | sudo socat /run/haproxy/admin.sock -
ss -ltnp | grep :1024
sudo journalctl -u haproxy -n 100 --no-pager | grep -i peer

show peers lists each peer in the section, its connection status, and the last successful sync timestamp. If a peer is Down, the journal will say why — usually network unreachable, TLS rejected, or hostname mismatch.

What’s happening

A peers section names a group of HAProxy instances that replicate stick-tables. Each member declares its peer name and listen address. When two peers can reach each other, they open a TCP connection on the peer port and stream updates: new keys, counter increments, expirations.

The protocol is push-only — each peer pushes its own table changes to all other peers. There is no leader. New entries arrive at peers in roughly real-time (sub-second for normal traffic).

Two important properties:

  • The local peer name must match the system’s resolvable hostname or be localpeer. HAProxy needs to identify “which entry in the peers section is me” — it does this by matching the peer line’s name to the hostname. The localpeer keyword overrides that.
  • The peer port is not encrypted by default. Use the modern bind syntax inside the peers section with ssl to require TLS, or rely on a private VLAN.

On reload, the new master inherits the peer connections from the old worker via FD passing. There is no resync storm.

When a peer crashes and comes back, it requests the full table from any reachable peer; once synced, normal incremental replication resumes.

The procedure

  1. Basic three-node peer group. Same peers block on every node, with each node’s hostname appearing as a peer:

    peers haproxy-cluster
        peer hap1 10.0.10.5:1024
        peer hap2 10.0.10.6:1024
        peer hap3 10.0.10.7:1024

    On hap1, the peer hap1 line is the local listener; the other two are remotes. The hostname hap1 (from hostname -s) must match the peer name.

  2. Reference the peer group from stick-tables. Add peers <name> to each table you want to replicate:

    backend be_app
        stick-table type ip size 200k expire 1h store http_req_rate(10s),conn_rate(10s) peers haproxy-cluster
        http-request track-sc0 src

    Every node uses the same backend and stick-table definition. The peer protocol does the syncing.

  3. Open the firewall. Port 1024 between peers, plus a UFW or security group rule:

    sudo ufw allow from 10.0.10.0/24 to any port 1024 proto tcp
    sudo ufw status verbose

    The peer port is bidirectional — each pair of peers opens one TCP connection between them; either side can initiate.

  4. Verify connectivity on bring-up. From each node, confirm it sees the others:

    echo "show peers" | sudo socat /run/haproxy/admin.sock -

    Output should show LOCAL for the current node and Up for each remote, with a recent last_status_change timestamp.

  5. Add TLS for the peer link. When the peer link traverses a network you don’t fully trust, wrap it in TLS:

    peers haproxy-cluster
        bind 10.0.10.5:1024 ssl crt /etc/haproxy/certs/hap1-peer.pem ca-file /etc/haproxy/ca/peer-ca.pem verify required
        server hap1
        server hap2 10.0.10.6:1024 ssl ca-file /etc/haproxy/ca/peer-ca.pem verify required sni str(hap2)
        server hap3 10.0.10.7:1024 ssl ca-file /etc/haproxy/ca/peer-ca.pem verify required sni str(hap3)

    The server syntax (instead of peer) is the newer form that exposes per-peer SSL options. Mix carefully — for plain text, the older peer syntax is shorter.

  6. Handle the resync window. On node restart, the joining peer requests a full table from a remote. During this window, ACLs that read the table see stale or empty data. Set resync-timeout and accept that rate limits may be briefly looser:

    peers haproxy-cluster
        peer hap1 10.0.10.5:1024
        peer hap2 10.0.10.6:1024
        resync-timeout 30s
  7. Plan for split-brain. If a network partition separates two peers, both keep counting. When they reconnect, the protocol merges — the peer with the higher count wins per entry. Rate limit counters double-count briefly during the merge; for sticky sessions, the more recent server-id wins. Document the behaviour; do not pretend it does not happen.

Common pitfalls

  • The local hostname must match the peer name. If hostname -s returns hap-prod-01.internal but the peers section says peer hap1 ..., HAProxy logs local peer name 'hap1' not found in peers section. Either fix the hostname or use localpeer hap1 in the global section.
  • Without peers, every reload wipes the table. We have seen this surface as “rate limits don’t seem to be working” — the engineer reloaded HAProxy ten minutes earlier and the table is starting fresh.
  • The default peer port is unencrypted. On a flat shared VLAN with other tenants, this leaks counters. Use a dedicated management VLAN or wrap with TLS.
  • Peers behind NAT need consistent reachable addresses. Two peers in different VPCs joined by a NAT gateway can talk to each other, but the address each sees depends on the NAT — get this right or peers loop trying to connect.
  • The peer protocol replicates table data, not server state, not config. disable server via runtime API on one node does not propagate. For per-node operator commands, run them on every node.

Stack Harbor wires peers into every multi-instance HAProxy fleet — TLS-wrapped peer link on a management VLAN, identical config on every node, and a sync verification step in the deploy script. For multi-region clients, peers across regions add latency to replication; we deploy per-region peer groups and accept that rate limits are per-region. This is how we run Multi-region Environments where the load balancer fleet itself spans multiple sites.