Zigbee2MQTT vs ZHA: Home Assistant Zigbee Integrations Compared
Two ways to talk Zigbee, one decision you'll live with for years

Contents
I once spent a Sunday evening re-pairing forty-two Zigbee devices one at a time, standing on chairs to reach ceiling sensors, because I’d decided — six months too late — that I’d picked the wrong integration. That is the whole reason this article exists. The choice between ZHA and Zigbee2MQTT looks like a five-minute decision when you’re setting up your first three bulbs. It isn’t. It’s a decision you live with for years, because migrating a meshed network of fifty devices from one coordinator software to another is exactly the kind of tedious evening you only want to do once, and preferably never.
People treat this like a religious war. It isn’t. Both are genuinely good projects, both are actively maintained, and both will run a mainstream smart home without complaint. But they differ in ways that matter once you’re past the toy stage — and the differences aren’t the ones the forum arguments obsess over. So before you plug in that coordinator stick, here’s the honest comparison I wish someone had handed me.
Why you’re standardising on Zigbee at all
If you’re on Home Assistant and you’ve sensibly decided to keep your home off the cloud, Zigbee is the obvious low-power radio for battery sensors, buttons, and switches. It meshes (mains-powered devices repeat signals for battery ones), it’s cheap, and it doesn’t depend on some vendor’s server staying online in 2029. The catch is that “Zigbee” is a radio standard, not a piece of software. Something has to sit between the radio and Home Assistant and turn raw Zigbee traffic into entities you can automate. That something is either ZHA or Zigbee2MQTT. Everything below is about which translator you put in that gap.
If you haven’t chosen a broker strategy yet, it’s worth reading up on what MQTT actually does first, because — as you’ll see — whether you already run a broker is one of the biggest factors in this decision.
How they differ architecturally
Both need a Zigbee coordinator — a USB stick or Ethernet adapter that contains the actual radio. The two chip families you’ll meet are Texas Instruments CC2652-based sticks (the zstack family) and Silicon Labs EFR32 Series 2 devices (the ember family). Either works with either integration. The difference is entirely in what runs on top.
ZHA is a first-party Home Assistant integration. It runs inside the HA process, talks directly to the coordinator over serial, and exposes each device as a native HA entity. No extra containers, no message broker, nothing else to keep patched. It’s the path of least resistance, and for a lot of people that’s the end of the discussion.
Zigbee2MQTT (Z2M) is a separate application. It owns the coordinator, decodes Zigbee messages itself, and publishes the results to an MQTT broker such as Mosquitto. Home Assistant then discovers those devices over MQTT. Your data path becomes:
| |
More moving parts, yes. But each part is independently inspectable — you can watch raw messages fly past on the broker with mosquitto_sub -t 'zigbee2mqtt/#' -v — and, crucially, the bridge isn’t chained to Home Assistant’s lifecycle. Hold that thought; it’s the single most underrated difference between the two.
Device support, and “it paired but does nothing”
The biggest practical difference is device compatibility. Zigbee2MQTT maintains a huge, community-curated database of device definitions — thousands of models, with awkward vendors handled explicitly. When a new sensor appears on AliExpress, support tends to land in Z2M first, often within days, because contributing an “external converter” is a well-trodden path.
ZHA has closed most of that gap. Its quirks system (the zha-device-handlers project) patches over vendors who deviate from the Zigbee spec, and those quirks now ship bundled with Home Assistant itself, so they arrive automatically with core updates rather than needing manual installation. For anything mainstream it’s a non-issue.
But “most” isn’t “all”. I still occasionally meet a three-quid off-brand temperature sensor that pairs happily in ZHA, shows up as a device, and then exposes precisely nothing useful — no temperature, no humidity, just a lonely LQI value. Move the same stick to Zigbee2MQTT and every attribute appears correctly, because someone already wrote the converter. If you buy IKEA, Aqara, Philips Hue, and Sonoff, both integrations handle it fine and this whole paragraph is irrelevant to you. If you’re the sort who buys obscure sensors because they’re three quid, Z2M’s database will quietly save you a lot of grief.
Running Zigbee2MQTT the sane way
If you go the Z2M route outside the Home Assistant add-on — which I’d recommend precisely for the decoupling — a Compose file is the clean approach:
| |
That by-id path is not a nicety — it’s the difference between a stable setup and a 2am debugging session. Plug in a second USB device and reboot, and the kernel may hand your coordinator /dev/ttyACM1 instead of /dev/ttyACM0. The by-id symlink is tied to the device’s serial number, so it never moves. This is the first thing I check when someone says “it was working yesterday”.
The matching configuration.yaml, in current Z2M schema, ties the bridge to your broker and coordinator:
| |
Two things to note. First, the serial.adapter key: recent Z2M wants you to declare the chip family explicitly (ember for Silicon Labs, zstack for TI) rather than guessing — set it wrong and the bridge simply won’t start. Second, the frontend gives you a genuinely useful web UI: a live mesh map, per-device link quality, and settings you’d otherwise edit by hand. ZHA’s built-in visualisation exists but is noticeably more basic.
The ZHA case, made fairly
ZHA’s advantage is simplicity and resilience-through-fewer-parts. There’s no broker to keep alive, no second container to update, one fewer thing to break, and no MQTT topic hierarchy to reason about. Pairing happens entirely in the HA UI — click “add device”, put the sensor into pairing mode, done. For a network of mainstream kit, ZHA is genuinely lower-maintenance, and the “everything lives in one place” story is real. If you don’t already run MQTT for anything else, standing up Mosquitto purely to serve Zigbee is a fair chunk of extra surface area for a benefit you may not need.
Coordinator backups deserve a specific mention, because both sides have improved here. ZHA can back up and restore the coordinator’s network state from within Home Assistant, which historically was the scary bit — lose that and you re-pair everything. Z2M keeps the equivalent state (including your all-important network key) in its z2m-data directory. Full backup/restore is officially supported for both zstack and ember coordinators, so migrating to a new stick of the same family is now routine. Migrating across families (TI ↔ Silicon Labs) usually works but isn’t officially guaranteed.
The difference that actually bit me
Here’s the flip side of ZHA’s simplicity, and the thing that cost me that Sunday evening: ZHA is coupled to Home Assistant’s lifecycle. Restart HA and your Zigbee network restarts with it. Take HA down for a fifteen-minute upgrade and your motion-activated lights are dead for fifteen minutes. Break your HA config with a typo and you’ve broken your Zigbee mesh too.
With Zigbee2MQTT, the bridge is a separate process. I can restart Home Assistant, upgrade it, or temporarily break it entirely, and the Zigbee mesh keeps humming. Any automation wired through MQTT rather than HA keeps firing. That decoupling is the main reason I run Z2M, and it’s the reason it belongs in the same “keep the important stuff independent of any single app” bucket as tricks like wake-on-LAN automation for powering servers on and off — you want the plumbing to survive the application layer wobbling.
Troubleshooting: the failure modes you’ll actually hit
Device pairs but no entities appear. In ZHA this usually means a missing quirk for an off-brand device. Check the ZHA logs for the manufacturer/model string, search the zha-device-handlers issues, and if there’s nothing, this is exactly the case where trying the same stick under Z2M is worth the hour.
Z2M refuses to start after an upgrade. Nine times out of ten it’s the serial.adapter field being unset or wrong for a schema that now demands it. Set adapter: ember or adapter: zstack to match your stick and re-read the changelog before upgrading across a major version — Z2M’s 2.x line changed several config defaults.
Interference and dropped devices. Zigbee shares the 2.4 GHz band with Wi-Fi. If distant devices keep going unavailable, your Zigbee channel is probably colliding with your Wi-Fi channel. Move Zigbee to channel 15, 20, or 25, keep the coordinator on a USB extension cable away from the machine (USB 3.0 ports are notorious 2.4 GHz noise sources), and add mains-powered repeaters. This one is coordinator-agnostic — it bites ZHA and Z2M equally.
Ghost / duplicate entities in Home Assistant. With Z2M, deleting a device only in HA leaves Z2M still publishing it, so it reappears. Remove devices from the Z2M frontend, not just from HA. This asymmetry — two places that both think they own the device — is the tax you pay for the extra layer.
Broker auth failures. If HA shows no MQTT devices, confirm Mosquitto is actually reachable and that the Z2M user has permission on the zigbee2mqtt/# topics. mosquitto_sub from a third machine is the fastest way to prove whether the problem is Z2M-to-broker or broker-to-HA.
A quick decision cheat-sheet
- Already run MQTT (for ESPHome, Tasmota, or voice assistants like Whisper and Piper)? The marginal cost of Z2M is near zero and you get the better device database. Lean Z2M.
- Want the absolute minimum to maintain? ZHA. One integration, no extras, no broker.
- Buying cheap or exotic devices? Z2M, for the converter database.
- Want the mesh to survive HA restarts and upgrades? Z2M, because the bridge is independent.
- Want the simplest single-backup story? ZHA — its state rides along in HA’s own backups; Z2M is one extra directory to remember.
Is it worth it, and who is this for?
I run Zigbee2MQTT, and if you already have a broker I’d nudge you the same way — chiefly for the device database and the operational decoupling from Home Assistant. That decoupling has saved me more than one late-night panic, and once you’re monitoring power draw and uptime across a lab it fits the same philosophy as tracking what your home lab actually costs: keep the components independent and inspectable.
But I won’t pretend ZHA is the wrong answer. If you’re building a tidy network of mainstream devices, don’t run MQTT for anything else, and value having one fewer service to patch, ZHA’s “nothing extra to run” simplicity is a real and lasting benefit — and the device gap is far smaller than the forum wars suggest. Pick ZHA if you weight simplicity; pick Z2M if you weight compatibility and decoupling. There is no wrong choice here, only a choice you’d rather not reverse.
Whichever you land on, do the one thing I skipped: write down your Zigbee network key and back up the coordinator state on day one. That’s the part you’ll genuinely regret skipping — and, unlike the ZHA-versus-Z2M debate, it’s the same advice regardless of which integration you choose.




