docs.na.id.au

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:

EntityMeaning
input_boolean.active“I am the active instance”. Each instance only ever sees its own boolean — it is not shared state.
rest_command.heartbeatGET to the partner’s webhook URL (kept in secrets).
Webhooklocal_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:

  1. input_boolean.activeoff
  2. wait 65 s
  3. input_boolean.activeon

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:

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:

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:

SecretWhat varies per instance
nameInstance name
internal_url / external_urlThis instance’s URLs
heartbeat_urlThe 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

Alternatives considered



Source Disclaimer