Runbook: Load balancer failure - recover and restore
1 · Prerequisites
Confirm every item is in place before any state change.
2 · Pre-checks
Read-only diagnostic commands. If any of these don't match expected output, stop and investigate further.
- · Verify HAProxy is running on both LBs (HA pair)
- · Verify VIP is on the active LB
- · Verify backends are healthy
- · Verify the LB configuration is in version control
3 · Procedure
Execute each step in order. Verify the expected output of a step before moving to the next.
- 1Identify the failure (LB down, backends down, routing issue)
- 2Check HAProxy logs and validate the config before any restart
- 3Check backend logs
- 4If LB down: failover to the backup LB
- 5If all backends are down together: find the shared cause before restarting anything
- 6If individual backends are down: diagnose, restart one at a time, confirm health between each
- 7If routing issue: check config, reload
- 8Verify recovery
- 9Document the incident
4 · Verification
Confirm the procedure actually fixed the problem.
- ✓Traffic is flowing through the LB
- ✓All backends are healthy
- ✓Health checks are passing
- ✓No errors in logs
5 · Rollback
If verification fails, undo the procedure in reverse order.
- ↶If the active LB is the problem, force the VIP to the backup
- ↶If a backend is the problem, remove it from the pool
- ↶Re-add when fixed
6 · Escalation
When the runbook isn't enough, contact:
- · If both LBs are down, escalate to the network team
- · If all backends are down, escalate to the application team
- · If the issue is a security breach, escalate to security
This runbook recovers from a load balancer failure. The LB can fail in several ways: the LB process, the network, or all backends. Each has a different recovery.
When to use this runbook
Use this runbook when:
- The LB is down (HAProxy is not responding).
- All backends are down (no healthy backend).
- Traffic is not flowing to the application.
- The VIP is not on any LB.
Inputs
Gather before starting:
- The LB configuration (HAProxy or other).
- The backend list.
- The VIP and DNS.
- Out-of-band access (console, IPMI, cloud).
Procedure
Step 1: Identify the failure
# Check LB status
sudo systemctl status haproxy
# Check VIP
ip addr show | grep <vip>
# Check backends
curl http://<backend>/healthThe failure type determines the response:
- LB down: HAProxy process is not running.
- Backends down: HAProxy is up but all backends are marked down.
- VIP not on any LB: keepalived is not working.
- Network issue: traffic is not reaching the LB.
Step 2: If the LB is down
Collect the evidence first. A restart destroys the only record of why the process stopped, and if the cause is still present the restart either fails identically or succeeds and fails again in ten minutes.
# Why did it stop? Read this before touching anything.
sudo journalctl -u haproxy -n 100 --no-pager
# Parse-only. Mutates nothing, starts nothing.
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# Is anything listening on the front ends?
sudo ss -tlnp | grep -E ':(80|443)'
# Preserve the log before rotation or a restart overwrites the window
sudo cp /var/log/haproxy.log /tmp/incident-$(date +%s).logOnce the journal has given you a cause, restart:
sudo systemctl restart haproxy
# If keepalived is also down, restart it after checking its journal too
sudo journalctl -u keepalived -n 50 --no-pager
sudo systemctl restart keepalivedIf HAProxy will not start, do not escalate to rebooting the VM or power-cycling the host. A reboot removes every remaining piece of evidence, and the process failed for a reason that will still be there afterwards. If the host itself is unreachable rather than the service, that is a host incident: fail the VIP over to the standby LB (see the rollback section) and treat the dead host separately.
Verify:
# The VIP should now be on this LB
ip addr show | grep <vip>
curl http://<vip>/healthStep 3: If backends are down
# Check each backend
for backend in web1 web2 web3; do
curl -s -o /dev/null -w "%{http_code}\n" http://$backend/health
doneRead the pattern before you read the individual hosts.
- Some backends down, the rest healthy: the fault is per-host. Work them one at a time.
- All backends down at once: the fault is almost never in all of them simultaneously. It is shared - a bad deploy, the database, an expired certificate, a DNS change, a configuration push. Restarting the backends cannot fix any of those, and it destroys the evidence needed to find them.
When the cause is shared, prove it from one host rather than guessing across all of them:
# Bypass the LB - talk to a single backend directly
curl -sv http://web1:8080/health
# What does that backend say about itself?
ssh web1 "sudo journalctl -u myservice -n 100 --no-pager"
# Is the shared dependency reachable from the backend?
ssh web1 "sudo ss -tnp state established | head; getent hosts db.internal"If one backend fails in isolation and the others are fine, diagnose then restart that host, and confirm it before touching the next:
ssh web1 "sudo systemctl status myservice"
ssh web1 "sudo journalctl -u myservice -n 100 --no-pager"
# Restart one host only
ssh web1 "sudo systemctl restart myservice"
# Confirm it is healthy before moving to the next host
curl -s -o /dev/null -w "%{http_code}\n" http://web1:8080/healthOne at a time, with a health confirmation between each, is what keeps the remaining capacity serving while you work.
The LB’s health check should mark the backend up after a successful health response.
Step 4: If the VIP is not on any LB
systemctl status keepalived --no-pager
sudo journalctl -u keepalived -n 100 --no-pager # VRRP transitions: "Entering MASTER STATE"
# Is the VIP actually present on an interface? This is the ground truth.
ip -brief addr show
# Parse-only config check - changes nothing
sudo keepalived --config-test -f /etc/keepalived/keepalived.confRun these on both load balancers before concluding anything. The failure modes look identical from one side: a VIP on neither host and a VIP on both hosts are different incidents with different fixes, and you cannot tell them apart from a single node.
Note --no-pager and -n 100 rather than tail -f: a runbook
step has to terminate so the next one can run. Keep the follow
form for watching a failover you are about to trigger, not for a
diagnostic step.
If keepalived is not running, restart it. If the VIP is on neither LB, the issue may be:
- Network partition.
- Both LBs have lower priority.
- Keepalived configuration error.
Step 5: If the issue is the LB configuration
# Check the configuration syntax
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# Reload
sudo systemctl reload haproxyIf the reload fails, revert to the last known good config:
sudo cp /etc/haproxy/haproxy.cfg.bak /etc/haproxy/haproxy.cfg
sudo systemctl restart haproxyStep 6: Verify
# LB is up
curl http://<vip>/health
# All backends are healthy
curl -s -o /dev/null -w "%{http_code}\n" http://<vip>/health
# Real traffic works
# Wait for the load balancer to send real traffic
# Or test with curl
curl http://<vip>/Step 7: Document
In the incident log:
- Time of failure.
- What failed (LB, backends, network).
- Recovery time.
- Improvements (HA pair, more capacity, better monitoring).
Common patterns
Every row is diagnosis first. The middle column is what you must establish before the right-hand column is safe to run.
| Symptom | Establish the cause | Then act |
|---|---|---|
| LB not responding | journalctl -u haproxy: OOM kill, config rejection, or clean stop? | OOM: fix memory pressure, then start. Config rejected: revert the config, then start. Clean stop: find what stopped it, then start. |
| All backends down | Curl one backend directly, not the VIP. If every backend fails identically, the cause is shared - deploy, database, certificate, DNS. | Fix the shared dependency. Restarting the pool cannot fix it and destroys the evidence. |
| Some backends down | Read that host’s service journal; compare against a healthy peer. | Restart that one host, confirm health, then move to the next. Never in a loop. |
| VIP on neither LB | journalctl -u keepalived on both: partition, both in BACKUP, or config error? | Restore the path or fix the priority. Forcing the VIP over a partition risks two masters. |
| 503 from LB | echo "show stat" | socat stdio /run/haproxy/admin.sock: which check is failing, and with what? | Fix the failing check. A 503 with all checks passing is a routing or ACL fault, not a backend fault. |
| Slow responses | Is it the backends (show stat queue and response times) or the LB itself (CPU, conntrack table full)? | Add capacity where the queue actually is. |
Knowledge check
Knowledge check · 5 questions
Q1. What is the first step in a load balancer failure?
Q2. When the LB is down, the application continues to work.
Q3. Which of the following are valid LB failure modes? Select all that apply.
Q4. The VIP is up, HAProxy is running, and all six backends went down within the same second. What do you do first?
Q5. HAProxy is not running. `journalctl -u haproxy` shows the kernel OOM-killed it. What does `systemctl restart haproxy` achieve?
Passing score: 75%. Answers are checked in this browser.