private@homelab: ~/latest
Home Assistant · Zigbee2MQTT · local control
private@homelab:~$ cat guides/article.md
· ·
8–12 minutes
read

Zigbee2MQTT last_seen Not Updating? What It Actually Tracks

Zigbee2MQTT's last_seen goes frozen, N/A, or oddly fresh on dead devices. What it tracks, why it misleads, and how to build a stale check that works.

A sensor’s last_seen field looks frozen. Or it just shows N/A. Or, more confusingly, it looks perfectly current, timestamped minutes ago, while the temperature or motion reading hasn’t actually changed in days. All three complaints show up constantly in Zigbee2MQTT’s GitHub issues, usually as isolated bug reports with no real explanation attached.

That’s because last_seen isn’t measuring what most people assume it measures. It’s not “is this device alive and reporting real data.” It’s something narrower, and the gap between those two ideas is exactly where the confusion comes from.

This is a research-synthesis piece built from Zigbee2MQTT’s own docs and GitHub issue threads, not a bench test. Everything below is local by nature: last_seen is generated entirely inside Zigbee2MQTT from Zigbee traffic and MQTT, with no vendor cloud involved at any point.

What last_seen actually is — and why it’s off by default

last_seen is disabled by default. The setting’s default value is literally disable, and it has to be turned on explicitly by setting advanced.last_seen to ISO_8601, ISO_8601_local, or epoch (Zigbee2MQTT settings reference). Until you set one of those, the field simply won’t appear in a device’s MQTT payload at all, which is what’s actually behind a large share of the “last_seen shows N/A” complaints (GH #9398). It’s a config default, not a bug, for a lot of people who hit this first.

Once enabled, the documentation defines it narrowly: the date and time of the last Zigbee message from that device. What the documentation doesn’t do is enumerate which kinds of message count, and that gap is where every confusing symptom below comes from. It doesn’t check whether the message carried a meaningful attribute report, whether the device is behaving normally, or whether the value it last reported is stale in any human sense.

It isn’t even strictly device-initiated. A 2019 report (GH #1651) covered devices that don’t send a Zigbee message when their state changes; the resolution was to also update the timestamp when Zigbee2MQTT’s own publish to the device succeeds. Defensible as a reachability signal, since the command got through, but it’s one more way the field drifts from “this device reported something.”

Why it can look current even when a device has gone quiet

The most-reported version of this is a timestamp that keeps advancing while the device’s actual readings sit unchanged. The largest single report, GH #23820 (“Last Seen is broken in several ways”), covers 22 devices across several brands — Bosch, Aqara and Tuya among them — where last_seen kept updating on devices the Device Map showed as not connected, and where devices reported a last_seen value but never updated their parameters. It was closed as not planned. GH #22551 describes the same shape of problem.

The mechanism those reports point back at is the definition itself. Any Zigbee traffic attributable to a device counts as “seen,” including network-level housekeeping unrelated to what the device is supposed to measure. Zigbee2MQTT doesn’t publish a list of which frame types move the timestamp, so the honest version is narrower than the forum folklore: last_seen tells you the coordinator heard something from that device. It does not tell you the device is still doing its job.

Note · scope

It is tempting to assume this only bites on mains-powered routers, and that a fresh last_seen on a sleeping battery sensor is therefore trustworthy. The reports don’t support that split — the device list in GH #23820 includes battery-powered motion and temperature/humidity sensors alongside mains-powered gear. Treat a fresh last_seen as weak evidence on any device class, not just routers.

If you’re chasing a device that seems fine on last_seen but wrong everywhere else, it’s worth checking whether the mesh itself is under strain first. Our write-up on the many-to-one route failure on Ember coordinators covers a specific routing failure mode that produces exactly this kind of “technically still there, functionally useless” symptom.

last_seen vs. availability — two different features, often confused

The other recurring source of confusion is treating last_seen and Zigbee2MQTT’s separate availability feature as the same thing. They aren’t, and they don’t even live in the same config block.

last_seen availability
What it measures Timestamp of the last Zigbee message attributable to the device An active online/offline determination
How it’s set Passive; updates automatically once advanced.last_seen is enabled Opt-in feature with its own config block, separate from advanced.last_seen
Behavior Never checks anything, only records Mains-powered devices are expected to check in (10 minutes by default) and get pinged if they don’t; battery devices can’t be pinged, so a much longer window applies (25 hours by default)
What it can’t tell you Whether the last message meant anything The precise timing of the last real report

last_seen is a data point. availability is a feature built to answer the actual online/offline question, by pinging devices where that’s possible and falling back to a timeout where it isn’t (device availability docs). If what you want is a Home Assistant entity that goes “unavailable” sensibly, availability is the thing to configure — not something you build on top of raw last_seen.

Known bugs worth knowing about before you trust either field

Beyond the semantic gap above, there are a few specific, documented bug patterns worth being aware of.

Symptom What’s actually happening Source
last_seen stale in Home Assistant, but device clearly still active Zigbee2MQTT’s internal database.db updates correctly, but the new value isn’t republished to the MQTT state topic — anything consuming MQTT sees the old value GH #7423, opened May 2021, reported for a Tuya TS0601 thermostat and a Moes MS-104Z switch module
last_seen column stops rendering in the Z2M frontend Cosmetic display bug reported after upgrading to Z2M 1.35; the column comes back after switching to another tab and returning, which is a workaround rather than a fix GH #20481, duplicated in #20513
Device Map shows disconnected, last_seen still ticks forward Non-attribute Zigbee traffic still counts as “seen,” even when the device’s real functionality has stopped GH #23820, also #22551
Warning

The database-vs-MQTT gap (GH #7423) means Zigbee2MQTT’s internal record can be accurate while everything downstream, Home Assistant included, works from a stale value pulled over MQTT. If a device’s real-world behavior and its last_seen disagree, rule out this republish gap before assuming the internal state is wrong.

None of this is a broker problem. Our MQTT broker security checklist is worth a look if you suspect something upstream on the MQTT side, but these bugs sit inside Zigbee2MQTT’s own publish path.

How current any of this is depends on your version. GH #7423 was opened in May 2021, and we found no changelog entry confirming a fix in the 2.x line — which is not the same as confirming it’s still broken. If your symptoms match that row, check the issue’s own status against the version you’re on rather than assuming the behavior is settled either way.

Enabling it properly

epoch gives you a Unix timestamp, useful if you’re doing your own math downstream. ISO_8601 gives you UTC as a sortable string. ISO_8601_local gives you the same thing in local time, usually the most readable if a human is glancing at a dashboard.

configuration.yaml (Zigbee2MQTT)
advanced:
  last_seen: ISO_8601_local

Whichever format you pick, remember what it’s actually recording. Enabling last_seen fixes the “why is this N/A” complaint. It does nothing about the deeper issue that the timestamp itself doesn’t distinguish a real report from background traffic.

Building a real stale-device check in Home Assistant

Because last_seen alone isn’t a reliable “is this device dead” signal, the more useful approach is a template sensor that compares the age of last_seen against a threshold you pick, rather than trusting the raw timestamp at a glance.

Note · before you start

Enabling last_seen in Zigbee2MQTT is not enough to get a Home Assistant entity out of it. Z2M publishes the value in the MQTT payload, but the matching Home Assistant entity is a diagnostic entity that arrives disabled by default. Enable it per device from the device page in Home Assistant, or set it enabled-by-default in Z2M’s Home Assistant device options. If the entity doesn’t exist, the template below reports every device as stale.

configuration.yaml (Home Assistant)
template:
  - trigger:
      - trigger: time_pattern
        minutes: "/15"
    binary_sensor:
      - name: "Living Room Motion Stale"
        unique_id: living_room_motion_stale
        device_class: problem
        state: >
          {% set last = states('sensor.living_room_motion_last_seen') %}
          {% if last in ['unknown', 'unavailable', 'none', ''] %}
            true
          {% else %}
            {{ (now() - as_datetime(last)) > timedelta(hours=6) }}
          {% endif %}

Swap in your own entity ID and pick a threshold matching how often the device is expected to report — a door sensor that only reports on state change needs a much longer window than a temperature sensor. The template assumes one of the ISO 8601 formats, since as_datetime parses a timestamp string; an epoch value is a number and needs different math. It also treats a missing or unknown state as stale, which matters for devices that dropped off the network entirely. If a device showing up here also has an orphaned or duplicate entry in your device list, our guide to Zigbee2MQTT ghost devices covers that separate failure mode.

Arguably none of this should need rebuilding downstream. An open feature request, GH #32152, asks for a “last updated” attribute distinct from “last seen,” so that “the coordinator heard from this device” and “this device reported a value” stop sharing one timestamp. Until something like that lands, the distinction has to be reconstructed in Home Assistant.

FAQ

Does last_seen mean a Zigbee device is currently online?
No. It means Zigbee2MQTT received some message attributable to that device then, including traffic unrelated to the device’s own function. Since GH #1651, a command Z2M successfully sends to a device also updates it.

Why does last_seen look current even though my sensor stopped updating?
Either non-attribute traffic still counts as “seen” while its own reports have stopped (GH #23820), or you’re hitting the database-vs-MQTT republish gap in GH #7423.

What’s the difference between last_seen and the availability feature?
last_seen is a passive timestamp with no logic attached. availability is a separate opt-in feature with its own config block that actively determines online/offline status, pinging mains-powered devices that miss their check-in and falling back to a long timeout for battery devices.

How do I enable last_seen if it’s showing N/A for every device?
Set advanced.last_seen to ISO_8601, ISO_8601_local, or epoch. The default is disable, and that’s the most common cause. Enabling it in Z2M doesn’t automatically give you a Home Assistant entity, though — that one arrives disabled by default.

Can I build a reliable “device went silent” alert using last_seen?
Not from the raw field alone. A template sensor comparing its age against a threshold gets you closer, but still can’t separate real attribute silence from background traffic.

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.

Sourced from Zigbee2MQTT’s configuration and device-availability docs plus five GitHub issue threads — #1651, #23820, #7423, #9398, #20481 — for: the disable default, the definition as last Zigbee message rather than last meaningful report, the outbound-command update, the database-vs-MQTT republish gap on a TS0601 thermostat and a Moes switch module, and the 1.35 display bug. What is not documented anywhere we could find is which specific frame types move the timestamp, so this piece stops where the record does instead of filling that gap with a mechanism.

Everything here stays local: last_seen, availability, and the template sensor all run on your own network. What depends on upstream is whether the #7423 and #20481 bugs are still open in the release you’re running.

Three things to confirm against your own setup. Check your Zigbee2MQTT version before assuming either linked bug applies. Confirm the Home Assistant last_seen entity is actually enabled before trusting the template. And treat a fresh timestamp as weak evidence on any device, router or battery sensor alike, rather than proof it still works.

local-firstHome Assistantno-cloud