A Home Assistant restart, a Mosquitto restart, or a Zigbee2MQTT container restart can all produce the same unsettling few seconds: every Zigbee entity flips to unknown or unavailable, even though the mesh itself hasn’t changed. The instinctive fix is Zigbee2MQTT’s retain option, and when retain: true changes nothing visible, that’s where the confusion sets in.
It often changes nothing because those three restarts are three different problems. Zigbee2MQTT already republishes device state after two of them without any help from retain, so when state is still missing, something else broke. Working out which restart you had is most of the diagnosis.
This isn’t the same failure as a corrupted device database, where devices genuinely vanish or lose their pairing — that’s covered in our guide to Zigbee2MQTT database.db corruption. Here every device is still paired. What’s missing is state: whatever subscribes to the MQTT topics has no idea what any device last reported. Everything below stays inside your own Zigbee2MQTT instance and broker, with no vendor cloud involved.
Which restart you had decides the mechanism
Zigbee2MQTT is not passive about restarts. It republishes cached device state both when it starts up itself and when it sees Home Assistant come back online, so in two of the three scenarios state returns without retain involved at all. That’s worth establishing before changing any configuration: a stuck unavailable usually points at the bridge’s own availability topic, not at device state.
| What restarted | What Zigbee2MQTT does | Why state can still look missing |
|---|---|---|
| Home Assistant only | Sees HA’s birth message and republishes discovery plus cached device state | Birth topic mismatch between the two sides, or bridge/state not republished alongside the states (GH #32067) |
| Zigbee2MQTT itself | Reads state.json on startup and publishes it back out (cache_state_send_on_startup, default true) | Rarely missing; the risk here is stale values replaying, not absent ones |
| MQTT broker only | Reconnects and carries on, but may not republish bridge/state | Retained messages are discarded unless the broker persists them, and HA reads bridge/state to judge the whole bridge (GH #4854) |
That first row is the one to understand. Home Assistant publishes a birth message when it starts, by default online on homeassistant/status, and Zigbee2MQTT responds by republishing discovery and cached device states. It only fires if both sides use the same topic — configurations carried over from older versions sometimes still have hass/status on one side (GH #27458).
What retain actually controls
retain isn’t a Zigbee2MQTT invention. It’s a flag defined in the MQTT protocol. When a message is published with it set, the broker stores it as the last known value on that topic and hands it to any client subscribing later. Without it, a fresh subscriber gets nothing until the next message arrives.
Zigbee2MQTT’s default is retain: false for every device (devices and groups docs). Which topics carry what, and which are retained regardless of this setting, is mapped out in our guide to Zigbee2MQTT’s MQTT topics.
Where that default actually bites is with subscribers that have no republish path of their own. Home Assistant gets its state back through the birth-message exchange above. An MQTT Explorer session, a Node-RED flow, or a dashboard reading the topics directly has no equivalent handshake, so for those clients retain is the only thing that makes state available the moment they connect (Home Assistant Community thread on Z2M message retention).
Retain is a per-topic MQTT setting, not something specific to the device or the reading it carries. Whether a client sees a value on reconnect depends on whether that exact topic was published with retain set.
state.json and the startup republish
The part that trips people up is assuming Zigbee2MQTT loses state when retain is off. It doesn’t. Zigbee2MQTT caches every device’s last known state and persists it to a state.json file in its data directory, governed by cache_state and cache_state_persistent, both defaulting to true. A third option, cache_state_send_on_startup, also defaults to true, and it’s what makes Zigbee2MQTT publish that cached state back out when it starts (all settings reference; GH #3496).
That’s the setting most of this confusion actually lands on. State survives a Zigbee2MQTT restart by default, through Zigbee2MQTT’s own cache rather than through anything the broker stored. retain covers a different gap: what a subscriber sees when Zigbee2MQTT hasn’t restarted and so has no reason to republish anything.
It also explains a side effect. Deleting state.json doesn’t buy a clean slate for long, since the file repopulates as devices report again. And the startup republish can push out hours-old values — an old motion detected state reappearing before the sensor has triggered — enough to misfire an automation. Setting cache_state_send_on_startup: false turns that republish off, at the cost of entities staying unknown until each device next reports.
The broker-restart edge case: bridge/state stuck offline
A related but distinct scenario is restarting only the MQTT broker. Zigbee2MQTT reconnects and keeps functioning, the mesh is untouched, but a documented issue is that it can fail to republish an online message to zigbee2mqtt/bridge/state after the broker comes back (GH #4854). Zigbee2MQTT publishes that topic as a retained message, so a reconnecting client would normally be handed it immediately — but a broker restart discards retained messages unless the broker is configured to persist them, leaving nothing to hand back. Home Assistant reads that topic to decide whether Zigbee2MQTT is reachable at all, so every device on that instance shows unavailable.
The same gap is reported from the Home Assistant side: device states republished on the birth message but not bridge/state alongside them, leaving every entity unavailable after a Home Assistant restart despite Zigbee2MQTT running normally (GH #32067).
If devices go unavailable right after a broker-only restart, or after a Home Assistant restart where the device states clearly did come back, don’t assume the Zigbee network failed. Check zigbee2mqtt/bridge/state before anything else. Restarting the Zigbee2MQTT service forces a fresh publish to that topic and typically clears it. This is a republish gap, not a mesh problem, so re-pairing devices or power-cycling the coordinator is the wrong first move here.
The group-retain gotcha
retain also applies to Zigbee2MQTT groups. But per a GitHub issue closed as not planned, adding retain: true to a group’s configuration isn’t exposed in the frontend, and setting it only through the config file doesn’t survive a restart (GH #28171, “Retain property for groups resets after restart”). Anyone relying on a group’s retained state downstream — a Node-RED flow reading group status on startup, say — hits this quietly, because the config file still shows the setting after it’s stopped working.
Verify group retain behaviour after every restart if anything downstream depends on it, rather than trusting the config file as the source of truth — there’s no frontend toggle to confirm or re-apply it. Our guide to Zigbee2MQTT groups vs. scenes covers the broader gap between how groups behave and how Home Assistant represents them.
retain, state.json, and availability_timeout are three different things
The other recurring confusion is folding this into availability_timeout, which is unrelated. That setting governs ongoing liveness detection — whether Zigbee2MQTT decides a device has gone offline because it hasn’t reported in a while (device availability docs). Retain and the state cache are narrower: what’s known immediately after a restart, before any new traffic. A device can have healthy availability and still show unknown briefly after Home Assistant restarts. What last_seen does and doesn’t tell you is covered in our guide to last_seen not updating.
| retain | state cache / state.json | availability_timeout | |
|---|---|---|---|
| What it is | MQTT protocol flag on published messages | Z2M’s local record of last known device state | Z2M’s ongoing offline-detection feature |
| What happens on restart | If true, broker replays the last message to new subscribers immediately | Z2M reads it back and republishes it on startup by default | Timer resets; doesn’t affect what’s known right after restart |
When retain is worth enabling, and the tradeoff
If Home Assistant is your only consumer of these topics, retain is often unnecessary — the birth-message republish already covers you, and a stuck entity is more likely a bridge/state problem. Retain earns its place when something else reads the topics directly and needs a value the instant it connects.
Both settings live in configuration.yaml, in different sections. Note that the devices block is keyed by IEEE address, not friendly name; keying it by friendly name fails silently.
advanced:
cache_state_send_on_startup: true
devices:
'0x00158d0001aabbcc':
friendly_name: bedroom_motion
retain: true
The tradeoff is stale values, and it’s worth being precise about which mechanism produces them. The startup republish replays whatever the cache holds when Zigbee2MQTT restarts; retain leaves the last payload on the broker indefinitely, so a subscriber connecting at any time gets it. My reading is that the safer default is leaving retain off for fast-changing sensors like motion and contact, and reserving it for state that rarely changes and where a stale read is harmless — a switch’s last known on/off position.
FAQ
Does enabling retain fix devices showing unavailable after a Home Assistant restart?
Usually it isn’t what’s needed. Zigbee2MQTT already republishes cached device states when Home Assistant announces itself on homeassistant/status. If entities are still unavailable, the likelier causes are a birth-topic mismatch or bridge/state not being republished alongside the states.
What is state.json in Zigbee2MQTT, and where is it stored?
Zigbee2MQTT’s own local record of every device’s last known state, written to a file in its data directory. It’s maintained regardless of the retain setting, and by default its contents are published back to the broker on startup.
Why do my Zigbee2MQTT devices show offline after just the MQTT broker restarts?
A known issue can leave Zigbee2MQTT failing to republish an online message to zigbee2mqtt/bridge/state, and the previously retained copy is gone unless your broker persists retained messages. Home Assistant reads that as the whole bridge being unreachable. Restarting Zigbee2MQTT typically clears it.
How do I stop old sensor values replaying when Zigbee2MQTT restarts?
Set cache_state_send_on_startup: false under advanced. Zigbee2MQTT keeps caching state but won’t publish it on startup, so entities stay unknown until each device reports again.
Can I set retain for a Zigbee2MQTT group?
Yes, but per a closed-as-not-planned GitHub issue, it isn’t exposed in the frontend and setting it in the config file doesn’t reliably survive a restart.
Is retain the same thing as availability_timeout?
No. Retain and the state cache concern what’s known right after a restart. availability_timeout decides when a device gets marked offline for lack of reporting.
What this explainer verified
This is a research-synthesis piece, not a bench test. No hardware was tested for it, and there’s no owned test-bench data behind any claim above.
The defaults come from Zigbee2MQTT’s own settings and availability documentation: retain defaults to false per device, while cache_state, cache_state_persistent and cache_state_send_on_startup all default to true. Those defaults are what make the three restart scenarios differ, and they’re the correction worth carrying away — state generally does survive a Zigbee2MQTT restart, through the cache rather than the broker.
The behavioural claims come from four GitHub threads: #3496 (retain and state.json, closed), #4854 (bridge/state offline after a broker-only restart, closed), #28171 (group retain not surviving a restart, closed as not planned), and #32067 (bridge/state not republished on the birth message). What depends on upstream is whether those behaviours persist in the version you run — closed is not the same as confirmed fixed across every release line.
Two things worth checking. Confirm the birth topic Home Assistant publishes on matches the one Zigbee2MQTT listens on, since a mismatch silently disables the republish that makes retain unnecessary. And if group retain matters downstream, verify it after every restart rather than trusting the config file.