A Zigbee2MQTT Home Assistant setup is three services in a chain: Zigbee2MQTT owns the coordinator stick, Mosquitto carries the messages, and Home Assistant’s MQTT integration turns them into devices through discovery. Run it if you buy off-brand Zigbee gear or already run a broker. On a small network of mainstream brands, built-in ZHA is less to maintain.
The extra hop is the whole trade. You get converters for 5,798 devices from 608 vendors on the supported devices list, a frontend with a network map, and a message bus anything can subscribe to. You also get one more container to update and one more suspect when a light ignores you. The click-by-click add-on install is in how to add Zigbee2MQTT to Home Assistant.
How does Zigbee2MQTT talk to Home Assistant?
Through MQTT topics, never directly. Zigbee2MQTT publishes each device’s state as JSON to zigbee2mqtt/<friendly_name>, accepts commands on the same topic plus /set, and posts discovery configs under the homeassistant prefix. Home Assistant reads those configs and builds entities. It never touches the radio, and Zigbee2MQTT never touches Home Assistant’s database.
The handshake runs both ways. Zigbee2MQTT keeps a retained online or offline state on zigbee2mqtt/bridge/state, per the topics reference. Home Assistant announces itself with online and offline on homeassistant/status, according to the MQTT integration docs, and that is the default status topic in Zigbee2MQTT’s Home Assistant settings. Restart either side and the other catches up.
Discovered devices land in Home Assistant’s normal device registry, and the integration page confirms you can rename entities from the Home Assistant UI without a restart. When something stops responding, triage from the radio up: if the device toggles from the Zigbee2MQTT frontend, look at MQTT or Home Assistant; if it doesn’t, it’s the mesh.
Should you run Zigbee2MQTT or ZHA?
Run Zigbee2MQTT if you buy Tuya-based or no-name devices, want per-device options and a network map in one frontend, or already keep a broker for other gear. Stay on ZHA if you buy mainstream brands and would rather not maintain a broker. The long version is ZHA vs Zigbee2MQTT.
The hard rule is one coordinator per application. The Zigbee2MQTT FAQ says an instance supports “a single dedicated Zigbee Coordinator radio adapter or module”, and a device joins only one coordinator, so leaving ZHA means a second stick or re-pairing everything. Budget an evening for the add-on route and a weekend if you’re migrating a full ZHA network.
What hardware works?
Any 64-bit box that runs Home Assistant comfortably runs Zigbee2MQTT; the stick and where it sits matter more than the CPU. The add-on page supports only aarch64 and amd64 and wants at least a Raspberry Pi 4, so an N100 mini-PC is plenty. Sizing by workload is in Home Assistant hardware requirements.
Coordinator. Buy a zStack (Texas Instruments) or EmberZNet (Silicon Labs) adapter. The FAQ says moving to a new adapter without re-pairing relies on backup and restore, which only the zstack and ember drivers implement. That’s the difference between a config edit and re-pairing the house. Picks are in best Zigbee coordinator stick.
Placement. The range and stability guide says a 50 cm USB extension “is already enough to reduce the interference”, and that an adapter close to a USB or HDMI port “can kill the radio signal entirely”. Mini-PCs cluster both on the back panel, so buy the cable.
Virtualized. On Proxmox, pass the stick by vendor and product ID (qm set <vmid> -usb0 host=<vendor>:<product>); the Proxmox wiki switches to the bus-port form when two identical devices are attached. Or skip passthrough: the adapters page recommends a remote adapter on wired Ethernet and warns that Wi-Fi ones may have reduced stability.
The stack: Home Assistant Container, Mosquitto, Zigbee2MQTT
On Home Assistant OS, install the Mosquitto and Zigbee2MQTT apps and skip to the next section. Container installs have no apps, per the installation docs, so you run all three; OS vs Container covers that choice.
services:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: homeassistant
network_mode: host
privileged: true
restart: unless-stopped
volumes:
- ./homeassistant:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
mosquitto:
image: eclipse-mosquitto:2
container_name: mosquitto
restart: unless-stopped
ports:
- "127.0.0.1:1883:1883"
volumes:
- ./mosquitto/config:/mosquitto/config
- ./mosquitto/data:/mosquitto/data
zigbee2mqtt:
image: ghcr.io/koenkk/zigbee2mqtt:2
container_name: zigbee2mqtt
restart: unless-stopped
depends_on:
- mosquitto
ports:
- "8080:8080"
environment:
- TZ=Europe/London
volumes:
- ./zigbee2mqtt:/app/data
- /run/udev:/run/udev:ro
devices:
# replace with your adapter's path from ls /dev/serial/by-id/
- /dev/serial/by-id/usb-YOUR_ADAPTER_ID-if00:/dev/ttyACM0
Home Assistant uses host networking, as in the official example, so it reaches the broker on 127.0.0.1:1883, while Zigbee2MQTT uses the service name. The loopback binding keeps the broker off your LAN; drop it if Tasmota plugs need in. Use your stick’s real /dev/serial/by-id/ path, as the Docker install page insists.
Then ./mosquitto/config/mosquitto.conf:
listener 1883
allow_anonymous false
password_file /mosquitto/config/passwd
persistence true
persistence_location /mosquitto/data/
log_dest stdout
Mosquitto 2.x needs that listener line: the migration notes say a config without one binds to loopback only, which inside a container locks everyone out. The persistence lines follow the image docs. Create one user per client; -c overwrites an existing file per the man page, so use it once:
docker compose run --rm mosquitto mosquitto_passwd -c /mosquitto/config/passwd z2m
docker compose run --rm mosquitto mosquitto_passwd /mosquitto/config/passwd homeassistant
Run docker compose up -d, finish onboarding on port 8080, then check that ./zigbee2mqtt/configuration.yaml has these sections, adding the last two by hand:
homeassistant:
enabled: true
mqtt:
server: mqtt://mosquitto:1883
user: z2m
password: your-z2m-password
serial:
port: /dev/ttyACM0
adapter: ember
frontend:
enabled: true
auth_token: a-long-random-string
availability:
enabled: true
Use adapter: zstack for a TI stick. Finally, add the MQTT integration in Home Assistant with broker 127.0.0.1, port 1883 and the homeassistant user. Home Assistant’s MQTT docs require a broker that supports MQTT 5; Mosquitto does.
What shows up in Home Assistant?
Each paired device appears under the MQTT integration with the entities its converter exposes, next to a Zigbee2MQTT bridge device. Four behaviours catch people out:
- Buttons. Since 2.0, “All action sensors are now disabled by default”, per the breaking changes list. Use MQTT device triggers instead. They appear only after each action has fired once, so press every button after pairing, then build YAML automations on them.
- Availability. Off by default. Once enabled, the availability docs expect mains devices to check in every 10 minutes and battery devices every 25 hours. Stop Zigbee2MQTT for more than 10 minutes and mains devices read offline until they next report. Constant flapping is a mesh problem: see Zigbee devices going unavailable.
- Groups. Discovery covers lights, switches, locks and covers; other group types are manual.
- Firmware. OTA updates appear as
updateentities, which replacedupdate_stateandupdate_availablein 2.0.
Storage and backups
The data directory is the network. configuration.yaml holds the network key and channel, database.db every paired device, and coordinator_backup.json the coordinator state on zStack and EmberZNet sticks. Treat the key as irreplaceable: the network settings page says changing it “requires re-pairing of all devices”.
On Container, copy ./zigbee2mqtt to the NAS nightly and let the NAS snapshot the share for history. Stopping the container for the minute rsync takes gives a consistent copy and stays inside the 10-minute availability window:
0 3 * * * cd /opt/stack && docker compose stop zigbee2mqtt && rsync -a zigbee2mqtt/ /mnt/nas/zigbee2mqtt/; docker compose start zigbee2mqtt
Or publish to zigbee2mqtt/bridge/request/backup, which returns a Base64 zip of the data folder minus logs. On Home Assistant OS, built-in backups can include installed apps and go to network storage. They’re encrypted, so keep the emergency kit somewhere other than the box being backed up.
The restore drill, twice a year: stop production Zigbee2MQTT, restore last night’s copy to a spare VM, move the stick over, start it and toggle three devices. Twenty minutes now beats re-pairing forty sensors at month nine.
Networking: keep it on the LAN
Nothing here needs to face the internet. The frontend docs list authentication as disabled by default, so anyone who reaches port 8080 can open the network or delete devices: set auth_token. The add-on serves the frontend through Home Assistant ingress instead, behind its login. Never port-forward 1883 or 8080. For remote access, Tailscale on the host reaches Home Assistant without opening a port. Cloudflare Tunnel is fine for the Home Assistant UI, but don’t publish the Zigbee2MQTT frontend through it.
Operations: updates and alerts
Zigbee2MQTT ships a release around the first of each month, going by its releases page. Most are uneventful. 2.0 switched action sensors off by default, exactly the kind of change that breaks button automations overnight.
- Container: no Watchtower. Pin the major tag (
:2), read the notes, then rundocker compose pull zigbee2mqttanddocker compose up -d zigbee2mqtt, per the Docker page. Update when you have time to fix a remote. - Add-on: leave auto-update off and set
watchdog: default, which the app docs say retries after 1, 5, 15, 30 and 60 minutes. - Alerts: have Home Assistant watch the bridge topic, swapping in your phone’s notify action:
- alias: "Zigbee2MQTT bridge offline"
triggers:
- trigger: mqtt
topic: zigbee2mqtt/bridge/state
value_template: "{{ value_json.state }}"
payload: "offline"
actions:
- action: persistent_notification.create
data:
message: "Zigbee2MQTT bridge went offline"
That catches clean stops. A dead host takes Home Assistant down with it, so that needs a check running somewhere else.
FAQ
do i need mqtt to use zigbee2mqtt with home assistant?
Yes: Zigbee2MQTT only talks to Home Assistant through MQTT, so you need a broker and the MQTT integration before any device appears. On Home Assistant OS, the Mosquitto broker app is the easy route, and Home Assistant can generate the integration’s credentials itself. On Container, run Mosquitto in Docker with password authentication.
can zigbee2mqtt and zha run at the same time?
Yes, but only on separate coordinators. Each needs its own stick, and every Zigbee device can belong to only one network, so a device paired to ZHA must be reset and re-paired to move. Running two meshes side by side is a reasonable way to migrate gradually; keep them on different Zigbee channels.
how many devices can zigbee2mqtt handle?
There is no fixed Zigbee2MQTT limit; capacity depends on the coordinator firmware and how many mains-powered routers the mesh has. The project’s FAQ debunks the old 20-device figure: that was the default CC2531 firmware’s limit on devices connected directly to the coordinator. Routers such as smart plugs and bulbs extend the network beyond it.
does zigbee2mqtt work without internet?
Yes, Zigbee2MQTT, Mosquitto and Home Assistant all run locally, so pairing, control and automations keep working when the internet is down. Firmware is the exception: OTA images come from the Koenkk/zigbee-OTA repository when internet access is available, though you can point Zigbee2MQTT at a local index and supply firmware files yourself.