private@homelab: ~/latest
local-first guides · privacy-aware · no noisy tracking
private@homelab:~$ cat guides/article.md
· ·
7–10 minutes
read

Zigbee2MQTT QoS Explained: What It Actually Guarantees

Zigbee2MQTT's qos setting only confirms delivery to your MQTT broker, not that the Zigbee radio command reached the device. Here's the gap explained.

Zigbee2MQTT lets you set a qos value on any device or group in configuration.yaml, and the official documentation names the field, calls it the QoS level for that device’s MQTT messages, then sends you to the generic npm mqtt package’s QoS reference for the actual explanation. That reference is correct about MQTT in general. It says nothing about what QoS covers in a Zigbee2MQTT setup specifically, and that’s where people get the wrong idea.

qos governs the MQTT hop only. That means delivery between Zigbee2MQTT and your broker, and from the broker on to whatever’s subscribed, like Home Assistant. It says nothing about whether the Zigbee radio command reached the physical end device. Those are two separate delivery hops. Setting qos: 2 on a light doesn’t mean the light guaranteed turned on. It means the MQTT message about the light guaranteed arrived.

Where qos lives, and what it doesn’t touch

qos is a per-device or per-group option under the devices and groups blocks in configuration.yaml, sitting alongside friendly_name, retain, optimistic and disabled. It’s easy to miss because it lives on the devices and groups reference, not on the broker connection settings page, which doesn’t mention it at all.

You don’t have to set it device by device. The device_options block holds defaults that apply to every device unless that device overrides them, and qos is one of them. Left unset anywhere, messages go out at QoS 0. Note that the devices block is keyed by IEEE address rather than friendly name, which fails silently if you get it wrong.

configuration.yaml
device_options:
  qos: 1

devices:
  '0x00158d0001a2b3c4':
    friendly_name: living_room_light
    qos: 2
    optimistic: false

groups:
  '1':
    friendly_name: downstairs_lights
    qos: 1

That qos value applies to the messages Zigbee2MQTT publishes about a device, its state and availability topics. It doesn’t apply to the set and get topics Zigbee2MQTT subscribes to, whose QoS is chosen by whatever publishes to them. And it has no effect at all on the Zigbee radio hop between the coordinator and the bulb.

Warning

Raising qos won’t fix a flaky Zigbee mesh. If a device intermittently misses commands, that’s a radio-layer problem, routing, interference, or weak signal, not something MQTT delivery guarantees can paper over. Check how Zigbee2MQTT structures its MQTT topics before assuming qos is the lever to pull.

What actually confirms the device got the command

The Zigbee hop isn’t unacknowledged. Zigbee2MQTT knows whether a command it sent was acknowledged, and a command that isn’t produces a failure in its log rather than silence. What’s missing is any path carrying that outcome back to the MQTT publisher as a delivery guarantee, which is precisely what the open discussion Transparent handling of qos 1/2 between mqtt and zigbee asks for.

The setting that gets closest sits three lines from qos in the same config block. optimistic defaults to true, and it’s what makes Zigbee2MQTT publish the new state immediately after sending a command, on the assumption it worked. Set optimistic: false for a device and the state topic only carries what that device actually reported back. That’s a real device-level confirmation signal, with the tradeoff that state updates arrive later and won’t arrive at all from a device that doesn’t report. It is not a QoS feature, and no qos value substitutes for it.

QoS 0, 1, 2: what each level actually promises

The three levels are standard MQTT protocol semantics, not something Zigbee2MQTT redefines. The level only describes the MQTT delivery contract, scoped to whichever hop it’s applied on.

QoS level MQTT guarantee Overhead Applies to, in Zigbee2MQTT
0 At-most-once (fire-and-forget, no acknowledgment) Lowest MQTT hop only, Zigbee2MQTT ↔ broker
1 At-least-once (may deliver duplicates) Moderate MQTT hop only, Zigbee2MQTT ↔ broker
2 Exactly-once (four-step handshake) Highest MQTT hop only, Zigbee2MQTT ↔ broker

One protocol rule matters more here than the levels themselves. A subscriber is delivered each message at the lower of two values: the QoS it was published with, and the QoS that subscriber asked for when it subscribed. That rule explains the most common complaint about this setting.

Why MQTT Explorer still shows qos 0

Two Home Assistant Community threads, posted roughly three years apart, describe the same symptom. Set qos: 2 on a device in configuration.yaml, restart, and MQTT Explorer still shows qos: 0 for that device’s messages. Neither thread has an accepted-solution marker, and the earlier one ends the same way.

Nothing is failing to apply. MQTT Explorer subscribes at QoS 0 and offers no way to change it, which is the subject of two open requests on its own tracker: Make QoS configurable and Can’t subscribe a topic with a QoS level > 0. By the minimum rule above, a message Zigbee2MQTT published at QoS 2 is handed to that client at QoS 0. The number in the viewer is a property of the subscription, not of the publish, so it will read 0 no matter what you configure.

To actually verify the setting, use a client that requests the level you want. mosquitto_sub -q 2 -t 'zigbee2mqtt/#' -v subscribes at QoS 2, so anything published below that still arrives at its own lower level, but a message published at 2 is delivered at 2. Neither community thread raises this, so treat it as the first thing to rule out rather than a confirmed post-mortem of those two cases.

Don’t confuse qos with the MQTT protocol version

Separate setting, separate docs page, easy to conflate because both live under “MQTT configuration” in spirit. Zigbee2MQTT’s MQTT protocol version (v4 by default) is configured independently of qos, and the docs recommend switching to v5 specifically if you’re using the per-device retention (message-expiry) option, which is itself a different setting from retain. If you’re troubleshooting qos and land on the protocol-version docs, you’re one page over from where you need to be.

Note · context

retain and qos are also unrelated settings that happen to sit next to each other in the same config block. retain controls whether the broker keeps the last message for late subscribers, while qos controls delivery reliability of each publish. See Zigbee2MQTT Retain vs state.json for how retain actually works. This article stays scoped to qos.

Should you raise QoS for Home Assistant discovery messages?

A closed feature request argued that Home Assistant discovery messages, published during Zigbee2MQTT’s startup burst at default QoS, can get silently dropped under load. The reporter said raising QoS to 1 for those messages fixed it, and asked for either a discovery-specific QoS option or a global default.

You can’t currently do the first one. No discovery-specific QoS setting appears in Zigbee2MQTT’s settings reference, the request is closed, and the reporter’s own fix was a modified build rather than anything configurable. The global default now exists in the form of device_options, but that governs device messages, so it isn’t a substitute here: Home Assistant discovery payloads go out on the discovery topic, not on a device’s own topics.

If you’re chasing entities that go missing after a Zigbee2MQTT restart and discovery seems to be the culprit, that’s a distinct troubleshooting path from generic device qos tuning. See the dedicated piece on Zigbee2MQTT discovery not working in Home Assistant for the broader diagnosis.

The broker side of this

Everything above assumes your broker is healthy and reachable. QoS settings can’t compensate for a broker that’s dropping connections, running out of memory, or misconfigured on authentication. Our checklist for securing a Mosquitto broker and the Zigbee2MQTT frontend is worth working through before you start tuning per-device QoS values.

I think the docs undersell this distinction badly. One sentence on the devices and groups page, saying that qos governs the MQTT hop and not Zigbee delivery, would have saved a lot of people the confused troubleshooting sessions that produced both community threads above. Sending readers to a generic MQTT reference answers a question they weren’t asking.

FAQ

Does a higher QoS mean my Zigbee command definitely reached the device?
No. qos only covers delivery between Zigbee2MQTT and your MQTT broker. Whether the Zigbee radio command was acknowledged by the device is a separate matter, visible in Zigbee2MQTT’s log and reflected in the state topic if you set optimistic: false.

Why does MQTT Explorer show QoS 0 even after I set qos: 2?
Because MQTT Explorer subscribes at QoS 0 and can’t be changed. A subscriber receives each message at the lower of the published QoS and its own subscription QoS, so that reading is about the viewer, not your configuration.

Can I set a default QoS for all my devices?
Yes. Put qos under device_options in configuration.yaml and it applies to every device that doesn’t set its own value.

Is QoS the same as retain?
No. retain decides whether the broker keeps the last message on a topic for clients that subscribe later. qos decides how hard the broker and client work to deliver each individual message.

Does raising QoS make an unreliable Zigbee device more reliable?
No, and it can hide the real problem. Intermittently missed commands are a radio-layer issue: routing, interference, or signal strength. Check link quality and mesh routing first.

What this explainer verified

This is a documentation and issue-tracker synthesis, not a bench test. No hands-on QoS behavior was measured against real hardware for this piece.

The official docs verify that qos is a per-device and per-group setting under the devices and groups blocks, that device_options supplies defaults for every device, and that optimistic defaults to true. The MQTT protocol version is a distinct setting on a different page. The “configured qos not reflected in MQTT Explorer” symptom is explained by the MQTT minimum-QoS rule combined with MQTT Explorer’s fixed QoS 0 subscription, though neither community thread confirms that diagnosis for its own case. None of this touches cloud dependency either way: QoS is an MQTT delivery setting between Zigbee2MQTT and your local broker, and it has no bearing on whether your setup stays local-first.

local-firstHome Assistantno-cloud