Zigbee2MQTT is best known as a Home Assistant add-on, but it does not need Home Assistant at all. At its core it is a bridge: it talks Zigbee to your sensors, bulbs and switches through a USB or network adapter, and publishes everything as MQTT messages. Anything that speaks MQTT can then use those devices: Node-RED, openHAB, a Python script, an ESP32 or a command-line tool. This guide sets up Zigbee2MQTT and Mosquitto in Docker on any Linux machine, pairs a first device and shows the MQTT topics you will use every day.
What you need
According to the Zigbee2MQTT documentation, there are three parts:
- A Zigbee adapter. The project recommends adapters based on Texas Instruments zStack or Silicon Labs EmberZNet firmware. Check your adapter's page in the documentation before installing, because some need extra settings.
- A host system. A Raspberry Pi, a mini PC or any Linux server with Docker. It needs an MQTT broker; Mosquitto is the recommended one.
- Zigbee devices to pair.
Plug a USB adapter into a USB extension cable, not directly into the computer. The documentation calls this the first thing to try for any trouble with timeouts, pairing or devices dropping off, because it helps avoid interference.
Find a stable path to the adapter
Device names like /dev/ttyUSB0 or /dev/ttyACM0 can change after a reboot or when you plug in another USB device. Use the stable path under /dev/serial/by-id/ instead:
ls -l /dev/serial/by-id/
You will see something like usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_...-if00-port0. Copy the full name. Network adapters (Ethernet or Wi-Fi coordinators) use a tcp://IP:PORT address instead.
Docker Compose with Mosquitto and Zigbee2MQTT
Create a directory, for example ~/zigbee, with this compose.yaml:
services:
mosquitto:
image: eclipse-mosquitto:2
container_name: mosquitto
restart: unless-stopped
ports:
- 1883:1883
volumes:
- ./mosquitto/config:/mosquitto/config
- ./mosquitto/data:/mosquitto/data
zigbee2mqtt:
image: ghcr.io/koenkk/zigbee2mqtt
container_name: zigbee2mqtt
restart: unless-stopped
depends_on:
- mosquitto
ports:
- 8080:8080
volumes:
- ./z2m-data:/app/data
- /run/udev:/run/udev:ro
environment:
- TZ=Europe/Warsaw
devices:
- /dev/serial/by-id/YOUR-ADAPTER-ID:/dev/ttyACM0
The Zigbee2MQTT part follows the official Docker example. /run/udev is mounted read-only so that Zigbee2MQTT can detect the adapter, ./z2m-data holds its configuration and network state, and restart: unless-stopped starts it again after a crash or reboot.
For Mosquitto, create mosquitto/config/mosquitto.conf:
listener 1883
allow_anonymous false
password_file /mosquitto/config/passwd
persistence true
persistence_location /mosquitto/data/
Create a user for Zigbee2MQTT and one for yourself:
docker compose run --rm mosquitto mosquitto_passwd -c /mosquitto/config/passwd z2m
docker compose run --rm mosquitto mosquitto_passwd /mosquitto/config/passwd me
Then start everything:
docker compose up -d
Onboarding
On the first start, when there is no configuration.yaml yet, Zigbee2MQTT runs an onboarding page on port 8080. Open http://SERVER-IP:8080 and fill it in:
- Adapter: the discovery usually finds it. Otherwise enter the path inside the container (
/dev/ttyACM0in the example above) and the adapter type (zstack,emberand so on). - MQTT server:
mqtt://mosquitto:1883. Inside the Compose network, containers reach each other by service name. - MQTT user and password: the
z2maccount you just created. - Network settings: let Zigbee2MQTT generate the PAN ID, extended PAN ID and network key. The documentation notes that changing the network key later means re-pairing every device, so decide once.
- Channel: the default is 11. The documentation recommends one of the ZLL channels 11, 15, 20 or 25. Pick one that does not overlap with your busiest 2.4 GHz Wi-Fi channel.
After you submit, Zigbee2MQTT writes configuration.yaml into z2m-data and starts. If something is wrong, for example the adapter path, the onboarding page comes back on the next start.
Secure the frontend and the files
The Zigbee2MQTT security guide makes one point very clearly: anyone who can reach the frontend or the MQTT broker has full control of the Zigbee network. So:
- Set a frontend token in
configuration.yaml, ideally pointing to a separate secrets file:
frontend:
enabled: true
auth_token: '!secret.yaml auth_token'
with auth_token: a-long-random-string in z2m-data/secret.yaml. The MQTT password and network key can be moved to the same file.
- Restrict the files:
chmod 600 configuration.yaml secret.yaml. - Do not expose port 8080 or 1883 to the internet. Use a VPN for remote access.
- Consider the rootless variant from the documentation, which runs the container under your user ID with the
dialoutgroup added for access to the adapter.
Pair a device
Open the frontend and click Permit join (All). Joining stays open for 254 seconds. Put the device into pairing mode, usually by resetting it as described on its page in the Zigbee2MQTT supported devices list. In the log you should see a line ending with device has successfully been paired.
Give the device a friendly name in the frontend, for example living_room_lamp. That name becomes part of its MQTT topic, so pick something short without spaces.
Use the MQTT topics
Everything Zigbee2MQTT does is visible and controllable over MQTT. The base topic is zigbee2mqtt by default.
Watch all messages:
mosquitto_sub -h SERVER-IP -u me -P 'your-password' -t 'zigbee2mqtt/#' -v
A sensor publishes its state as JSON on zigbee2mqtt/FRIENDLY_NAME, for example temperature, humidity and battery level.
Switch a lamp on:
mosquitto_pub -h SERVER-IP -u me -P 'your-password' \
-t 'zigbee2mqtt/living_room_lamp/set' -m '{"state": "ON", "brightness": 150}'
If you prefer plain values, publishing ON to zigbee2mqtt/living_room_lamp/set/state does the same as the JSON message.
Read a value on demand by publishing {"state": ""} to zigbee2mqtt/FRIENDLY_NAME/get. What can be read or set for each device is listed under Exposes on the device page.
Manage the bridge over MQTT too:
zigbee2mqtt/bridge/request/permit_joinwith{"time": 254}opens joining without the frontend,zigbee2mqtt/bridge/devices(retained) lists all paired devices,zigbee2mqtt/bridge/stateshows whether the bridge is online,zigbee2mqtt/bridge/request/backupreturns a ZIP of the data folder encoded in Base64.
From here, Node-RED, openHAB or your own code can subscribe and publish on these topics.
Backups and updates
The z2m-data directory is your Zigbee network: configuration, network key and device database. Back it up regularly together with mosquitto/config. With zStack and EmberZNet adapters, Zigbee2MQTT also stores a coordinator backup in coordinator_backup.json, which helps when you need to restore or move the network.
To update:
docker compose pull
docker compose up -d
Read the release notes before major version upgrades and take a backup first.