The HAProxy runtime API is a Unix socket (or TCP socket) that accepts text commands — disable a server, swap a certificate, change weights, query stats — without reloading. It is what makes hot operations possible on a live load balancer. This article covers the socket itself, the production commands we rely on, and the access pattern that gives an operator team safe shared use without sudo to every command.
How to verify
If the socket exists, you can ask HAProxy what it knows about itself.
ls -l /run/haproxy/admin.sock
echo "show info" | sudo socat /run/haproxy/admin.sock - | head -20
echo "show stat" | sudo socat /run/haproxy/admin.sock - | column -ts,
echo "show servers state" | sudo socat /run/haproxy/admin.sock -
show info is the version, uptime, process limits, and current concurrency. show stat is the same CSV the stats page exposes. show servers state is per-server: name, address, status, weight, check status.
What’s happening
The runtime API is enabled by stats socket in the global section. The standard production line:
global
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
mode 660— file permissions on the socket. With grouphaproxy, the haproxy user and the haproxy group can both write.level admin— the access level.useris read-only,operatoradds server enable/disable,adminis full control including config edits.expose-fd listeners— required for hot reload (master-worker mode) to pass listening sockets to the new worker.
You talk to it with any tool that can send text to a Unix socket: socat, netcat -U, hapee-lb cli (HAProxy Enterprise), or HAProxy’s own dataplaneapi (a separate process that wraps the socket in REST).
The API is stateless per-command — connect, send one line, read response, disconnect. For interactive use, prepend with set timeout 1d so the socket does not close on you.
The procedure
-
Standard one-liner pattern. Pipe a command into
socat:echo "show stat" | sudo socat /run/haproxy/admin.sock - echo "show table be_app" | sudo socat /run/haproxy/admin.sock - echo "show servers state" | sudo socat /run/haproxy/admin.sock -The trailing
-tells socat to use stdin/stdout.sudois needed because the socket is owned by root unless you set permissions otherwise. -
Disable, drain, enable a server. The three operator commands you use most:
echo "disable server be_app/app1" | sudo socat /run/haproxy/admin.sock - echo "set server be_app/app1 state drain" | sudo socat /run/haproxy/admin.sock - echo "set server be_app/app1 state ready" | sudo socat /run/haproxy/admin.sock - echo "enable server be_app/app1" | sudo socat /run/haproxy/admin.sock -drainis the polite path — no new sessions, existing sessions finish.disableimmediately marks the server as MAINT — connections are sent elsewhere. The state change is not persisted in config; on reload, the server returns to whatever itsdisabledkeyword in the config says. -
Adjust server weight live. Useful for canary rollouts or slow drains:
echo "set server be_app/app2 weight 0" | sudo socat /run/haproxy/admin.sock - echo "set server be_app/app2 weight 50" | sudo socat /run/haproxy/admin.sock - echo "set server be_app/app2 weight 100" | sudo socat /run/haproxy/admin.sock -weight 0is the same as drain at the algorithm level — no new sessions. Then ramp up. -
Swap a certificate without reload. When you have rotated a Let’s Encrypt cert:
echo "set ssl cert /etc/haproxy/certs/example.com.pem <<EOF" | sudo socat /run/haproxy/admin.sock - # paste new PEM, then echo "EOF" | sudo socat /run/haproxy/admin.sock - echo "commit ssl cert /etc/haproxy/certs/example.com.pem" | sudo socat /run/haproxy/admin.sock -The two-step (
setthencommit) is the transactional pattern: HAProxy parses the new cert, validates it, and only swaps the in-memory bundle oncommit. If the cert is malformed,commitfails and the old cert stays live. See HAProxy cert rotation. -
Manipulate stick tables. Look up an entry, add an entry, clear the table:
echo "show table be_app key 192.0.2.10" | sudo socat /run/haproxy/admin.sock - echo "set table be_app key 192.0.2.10 conn_rate(10s) 100" | sudo socat /run/haproxy/admin.sock - echo "clear table be_app" | sudo socat /run/haproxy/admin.sock - -
Interactive session. Some workflows want a long-lived prompt:
sudo socat - /run/haproxy/admin.sockThen type
prompt,set timeout 1d, and now each line is its own command. Typehelpfor the full command list at the currentlevel. -
Production access pattern — sudoers, not socket perms. Giving operators
chmod 666on the socket is the wrong fix. Instead, drop a sudoers line that lets the operator group run a specific wrapper script:# /etc/sudoers.d/haproxy-runtime %ops ALL=(root) NOPASSWD: /usr/local/sbin/hap-runtime# /usr/local/sbin/hap-runtime #!/bin/sh exec socat /run/haproxy/admin.sock -The wrapper does nothing but forward to the socket — but it does so under sudo with a logged audit trail, and the operators do not see the raw socket. For TCP access from a control plane (Kubernetes Operator, dataplaneapi), use the TCP variant of
stats socketwith mTLS.
Common pitfalls
set timeout 1dis per-session. If you reconnect, the default 30s timeout returns and idle sessions get cut off mid-troubleshoot.disable serverdoes not persist across reloads. If you disable a server and then someone reloads HAProxy 20 minutes later, the server is back in rotation. For persistent state, edit the config and reload.- Commands at
level usersilently return “Permission denied” — if a command does nothing and you have no error, checkshow infoand verify the socket’s level. set ssl certrequiresexpose-fd listenersin thestats socketline; without it, certificate swaps via runtime fail with an obscure error.- The runtime API on a TCP socket without mTLS is a remote control plane for your load balancer. We have seen this exposed on private networks that turned out to be flatter than people thought. Always mTLS or stay on Unix sockets.
Stack Harbor wraps the runtime API behind an ops runner — a small CLI that wraps socat with audit logging, command validation, and dry-run support. Operators issue named operations (“drain be_app/app1”, “rotate cert example.com”), not raw socket commands; every action is logged with operator identity. This is part of how we run Managed Operations for clients with shared on-call rotations.