The idea
High Availability (Active/Standby)
How two Home Assistant instances are run in an active/standby pair that fails over automatically — and the patterns that make two machines running identical config safe.
The idea
Run two instances on the same LAN. The active instance serves the dashboard, voice control, and automations; the standby waits, watching for the active one to die. Failover happens automatically through two small automations. When the active instance crashes (power loss, crash, reboot), the standby notices within about a minute and promotes itself. When the old instance recovers, it demotes itself — failback is automatic and symmetric, with no manual “switch back” step and no split-brain.
┌──────────────────────────┐ LAN, once per minute ┌──────────────────────────┐
│Instance A (active) │─────────────────────────►│Instance B (standby) │
│ │HTTP GET while A is active│ │
│input_boolean.active=ON │ (rest_command.heartbeat) │input_boolean.active=ON │
│heartbeat timer │ │watchdog (restart mode) │
└──────────────────────────┘ └──────────────────────────┘
A dies? → pats stop → B's watchdog fires → B becomes active
B dies? → A's webhook calls fail harmlessly → nothing changes
Key state:
| Entity | Meaning |
|---|---|
input_boolean.active | “I am the active instance”. Each instance only ever sees its own boolean — it is not shared state. |
rest_command.heartbeat | GET to the partner’s webhook URL (kept in secrets). |
| Webhook | local_only, GET only — LAN traffic only. |
The two instances do not share state: no database replication, no MQTT bus. Consistency comes entirely from the watchdog invariant — only an instance that is not being patted may be active.
The two automations
1. Heartbeat timer — “pat the standby’s watchdog”
While this instance is active (input_boolean.active is on), a
time_pattern trigger fires at the top of every minute and a state trigger on
input_boolean.active makes it fire immediately when the instance becomes
active after a restart. The action fires rest_command.heartbeat — a GET at
the partner’s webhook.
2. Watchdog — the clever part
Mode is restart. Triggers are (a) a webhook hit from the partner, or (b)
homeassistant.start. The action is:
input_boolean.active→ off- wait 65 s
input_boolean.active→ on
Because the mode is restart, every incoming heartbeat re-arms the whole
sequence from the top: the boolean goes off, the delay restarts, and the
boolean never gets to come back on. The 65 s delay is deliberately just longer
than the 60 s pat interval:
- Partner alive → a pat arrives every 60 s → the delay is re-armed before
it can complete → the instance sits in standby. The
homeassistant.starttrigger arms the watchdog immediately at boot, so a freshly started instance defaults to standby and only promotes itself if the pats never arrive. - Partner dead → the next pat never arrives → the last-armed 65 s delay
runs to completion →
input_boolean.activeturnson→ this instance is active. Its heartbeat timer starts patting the dead partner, which fails harmlessly.
Failover timing: because the delay is armed by the last successful pat, failover completes at most about 65 s after the last pat — worst case roughly a minute and five seconds after the failure, best case a few seconds.
The critical pattern: gate automations on input_boolean.active
Because both instances run the same automation files, any automation that
must fire on one machine only carries a condition on
input_boolean.active being on. On the standby the boolean is off (the
watchdog keeps it that way), so the condition fails and the automation never
runs — no double-fired side effects, no split-brain.
condition:
- condition: state
entity_id: input_boolean.active
state: 'on'
Not every automation needs the gate. The rule of thumb:
- Idempotent / hardware-tolerated actions — skip the gate and let both instances run in parallel. Opening roller shutters at a set time sends the same command twice and the hardware ends up in the same place; turning a battery charger off at a set time is harmless to repeat.
- Actions with global side effects — SMS, TTS announcements, notifications, arming the alarm — must carry the gate, or every family member gets two texts.
Shared config, per-instance secrets
Both instances pull from the same git repository. Anything that must differ
between the machines lives in each instance’s local, git-ignored
secrets.yaml and is referenced with !secret <name> — the secret acts as a
per-instance variable inside the shared YAML:
| Secret | What varies per instance |
|---|---|
name | Instance name |
internal_url / external_url | This instance’s URLs |
heartbeat_url | The partner’s webhook URL |
Workflow: edit the shared YAML, commit, pull on both instances. Each instance is also independently reachable — its own subdomain and its own nginx reverse proxy on the same server (see Deployment).
Gotchas worth knowing before you build this
- Network reachability is a single point of failure. A firewall or VLAN change between the two hosts looks identical to a dead partner — the standby will promote itself. Test after any router change.
- The webhook must stay
local_only-able. Pats only work on the LAN; two instances on different subnets/VLANs need this revisited. - External assistants connect to one instance only. A Google Assistant / HomeKit project can only meaningfully talk to one instance. With both instances exposing the same entities, the standby effectively has to be disconnected from the assistant (or you accept that requests may land on the wrong instance). Worth designing deliberately.
- Single-connection hardware needs a device per instance. Bluetooth bridges and similar can only be paired with one HA instance at a time — each instance needs its own dedicated bridge, and BLE devices only reach whichever instance owns the bridge.
- Add failover alerting. A useful extra automation: notify (SMS) when
input_boolean.activeflips toon— a promotion almost always means the other instance is down.
Alternatives considered
- MQTT heartbeat. The original design published the local IP to an MQTT topic once per minute and had each instance watch the broker. It worked, but added a third moving part (the broker) that had to be up for the cluster to function. The webhook design removed the broker entirely: the “bus” is just the partner’s own API, and the partner is the health check.
- Keepalived / VIP failover. Works, but moves the problem to the network layer and doesn’t help with automations and integrations that are inherently per-instance (Google Home, BLE bridges, local webhooks). The in-HA watchdog keeps everything visible and debuggable inside the dashboard.
Related
Source Disclaimer
- AI Generated
- Human Generated
- AI Edited
- Human Edited