Skip to content

HAProxy runtime API

Talk to HAProxy live via the admin socket — disable servers, swap certificates, query stats, and the safety pattern we wrap around it for shared operator access.

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 group haproxy, the haproxy user and the haproxy group can both write.
  • level admin — the access level. user is read-only, operator adds server enable/disable, admin is 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

  1. 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. sudo is needed because the socket is owned by root unless you set permissions otherwise.

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

    drain is the polite path — no new sessions, existing sessions finish. disable immediately marks the server as MAINT — connections are sent elsewhere. The state change is not persisted in config; on reload, the server returns to whatever its disabled keyword in the config says.

  3. 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 0 is the same as drain at the algorithm level — no new sessions. Then ramp up.

  4. 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 (set then commit) is the transactional pattern: HAProxy parses the new cert, validates it, and only swaps the in-memory bundle on commit. If the cert is malformed, commit fails and the old cert stays live. See HAProxy cert rotation.

  5. 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 -
  6. Interactive session. Some workflows want a long-lived prompt:

    sudo socat - /run/haproxy/admin.sock

    Then type prompt, set timeout 1d, and now each line is its own command. Type help for the full command list at the current level.

  7. Production access pattern — sudoers, not socket perms. Giving operators chmod 666 on 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 socket with mTLS.

Common pitfalls

  • set timeout 1d is per-session. If you reconnect, the default 30s timeout returns and idle sessions get cut off mid-troubleshoot.
  • disable server does 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 user silently return “Permission denied” — if a command does nothing and you have no error, check show info and verify the socket’s level.
  • set ssl cert requires expose-fd listeners in the stats socket line; 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.