HAProxy’s hot-reloading feature is the magic trick that lets you upgrade without anyone noticing.
Let’s see it in action. Imagine you have HAProxy running with a configuration that exposes a service on port 8080, and you want to upgrade to a newer version.
# Current HAProxy process ID
cat /var/run/haproxy.pid
# Back up your current configuration
cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.bak
# Install the new HAProxy binary (e.g., using your package manager)
# apt-get update && apt-get install haproxy
# or
# yum update haproxy
# Test the new configuration syntax
haproxy -c -f /etc/haproxy/haproxy.cfg
# If the syntax is good, gracefully reload HAProxy with the new binary
# This is the core of zero-downtime upgrades.
# The old HAProxy process will continue serving traffic.
# The new HAProxy process will be started with the new binary and the same configuration.
# Old processes will gradually shut down as their connections drain.
kill -USR2 $(cat /var/run/haproxy.pid)
# Verify the new process is running and serving traffic
ps aux | grep haproxy
# You should see two haproxy processes for a short while.
# The old one will have a parent PID that is the PID of the new one.
# Check the old PID file to confirm it's been updated to the new process ID
cat /var/run/haproxy.pid
The fundamental problem HAProxy’s hot-reloading solves is stateful connection management during process restarts. Traditional applications, when restarted, would drop all active connections, causing brief interruptions. HAProxy, however, allows a new process to be spawned alongside the old one. The old process, upon receiving the USR2 signal, stops accepting new connections but continues to gracefully close existing ones. Meanwhile, the new process takes over accepting new connections immediately. This "old process hands off to new process" dance is what makes the upgrade invisible to your users.
The USR2 signal is the key. When HAProxy receives USR2, it forks a new process using the new binary (which must be in the same location or a known alternative). This new child process inherits the configuration and the listening sockets from the parent. The parent process then writes its own PID to a new PID file (often haproxy.pid.oldbin). The new process updates the primary PID file (haproxy.pid) to point to itself. The old process, now aware it’s the superseded one, will continue to serve its existing connections until they are all closed, at which point it exits.
The critical levers you control here are the signals sent to the HAProxy process and the location of the PID file. By default, HAProxy writes its PID to /var/run/haproxy.pid. The USR2 signal tells the current process to spawn a new one from the new binary and then gracefully exit after draining connections. The HUP signal, on the other hand, tells the current process to re-read its configuration file but not to spawn a new process or exit. This is useful for applying configuration changes without restarting, but it doesn’t help with upgrading the binary itself.
Most people understand that HUP reloads config. What’s less commonly appreciated is how USR2 orchestrates the binary upgrade. The parent process doesn’t just exit; it actively manages the handover. It keeps its sockets open until the child process is ready and then signals the child to start accepting connections. The parent then waits for its own connections to drain. This explicit handoff mechanism, rather than a simple kill-and-restart, is what prevents connection drops.
The next logical step after a successful binary upgrade is to manage configuration changes that might be specific to the new version.