Zigbee2MQTT connects Zigbee devices (bulbs, plugs, sensors, switches) to an MQTT broker through a USB coordinator, so you can control them locally without the manufacturers' hubs or clouds. Every device becomes a set of MQTT topics that Home Assistant, Node-RED or your own scripts can read and write. In this tutorial you will run Zigbee2MQTT 2.x in Docker on Ubuntu 24.04, connect it to a Mosquitto broker, pair your first device and make it appear automatically in Home Assistant.
Prerequisites
Zigbee is a short-range radio, so Zigbee2MQTT must run on a machine physically close to your devices, such as a mini PC, a NUC or a Raspberry Pi 4/5, not on a remote cloud server. You need:
- A local machine running Ubuntu 24.04 LTS (server or desktop, amd64 or arm64) with a non-root user that has
sudoprivileges. - Docker Engine and the Docker Compose plugin installed from Docker's official repository.
- A Mosquitto broker on the same machine with password authentication, listening on port 1883.
- A supported Zigbee coordinator. Common choices:
| Coordinator | Chip | adapter value |
|---|---|---|
| SONOFF Zigbee 3.0 USB Dongle Plus (ZBDongle-P) | TI CC2652P | zstack |
| SONOFF Zigbee 3.0 USB Dongle Plus (ZBDongle-E) | Silicon Labs EFR32MG21 | ember |
| SMLIGHT SLZB-06 (Ethernet) | TI CC2652P | zstack |
| ConBee II / ConBee III | deCONZ | deconz |
Update the coordinator to the firmware recommended on the Zigbee2MQTT "Supported Adapters" page before you start; old firmware is the most common cause of unstable networks.
Step 1 - Identifying the coordinator
Plug the coordinator into the machine, ideally through a short USB extension cable: USB 3 ports and SSDs create interference in the 2.4 GHz band that Zigbee uses. Linux names serial devices /dev/ttyUSB0 or /dev/ttyACM0 in plug-in order, which can change after a reboot, so use the stable path under /dev/serial/by-id/:
ls -l /dev/serial/by-id/
lrwxrwxrwx 1 root root 13 Sep 25 10:02 usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_4c7f6a2b-if00-port0 -> ../../ttyUSB0
Copy the full usb-... name; you will map it into the container in Step 3.
If the directory does not exist, the adapter was not detected. Check the kernel log right after plugging it in:
sudo dmesg | tail -n 20
Step 2 - Creating an MQTT user for Zigbee2MQTT
Give Zigbee2MQTT its own broker account. The command prompts for a password:
sudo mosquitto_passwd /etc/mosquitto/passwd zigbee2mqtt
sudo chown mosquitto:mosquitto /etc/mosquitto/passwd
sudo systemctl restart mosquitto
If your broker uses an ACL file, allow this user to read and write its own topic tree and the Home Assistant discovery topics:
user zigbee2mqtt
topic readwrite zigbee2mqtt/#
topic readwrite homeassistant/#
Step 3 - Writing the Compose file
Create a directory for the stack. Zigbee2MQTT stores its configuration, device database and network keys in the data subdirectory:
sudo mkdir -p /opt/zigbee2mqtt/data
cd /opt/zigbee2mqtt
sudo nano compose.yaml
Paste the following, replacing the usb-... path with the one from Step 1 and the time zone with your own:
services:
zigbee2mqtt:
image: ghcr.io/koenkk/zigbee2mqtt:latest
container_name: zigbee2mqtt
restart: unless-stopped
network_mode: host
volumes:
- ./data:/app/data
- /run/udev:/run/udev:ro
environment:
- TZ=Europe/Madrid
devices:
- /dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_4c7f6a2b-if00-port0:/dev/ttyUSB0
network_mode: host lets the container reach Mosquitto on localhost:1883 and publishes the web interface on port 8080 without extra port mappings. The devices entry maps the stable host path to /dev/ttyUSB0 inside the container, which is the path you will use in the configuration.
NoteWith a network coordinator such as the SLZB-06, remove the
devicesblock entirely; Zigbee2MQTT connects to it over TCP.
Step 4 - Configuring Zigbee2MQTT
Create the configuration file before the first start:
sudo nano /opt/zigbee2mqtt/data/configuration.yaml
homeassistant:
enabled: true
frontend:
enabled: true
port: 8080
mqtt:
base_topic: zigbee2mqtt
server: mqtt://localhost:1883
user: zigbee2mqtt
password: your_mqtt_password
serial:
port: /dev/ttyUSB0
adapter: zstack
advanced:
network_key: GENERATE
pan_id: GENERATE
ext_pan_id: GENERATE
log_level: info
Set adapter to the value for your coordinator from the table in the prerequisites. For a network coordinator, set port to its address instead, for example tcp://192.168.1.50:6638.
The three GENERATE values tell Zigbee2MQTT to create a random network key and network identifiers on the first start and write them back into this file. That key encrypts all Zigbee traffic in your network, so keep a backup of configuration.yaml and never share it. The file also holds the MQTT password, so restrict it:
sudo chmod 0600 /opt/zigbee2mqtt/data/configuration.yaml
Step 5 - Starting Zigbee2MQTT
Start the container and follow the log:
cd /opt/zigbee2mqtt
sudo docker compose up -d
sudo docker compose logs -f
A successful start ends with lines like these:
zigbee2mqtt | [2026-09-25 10:15:31] info: z2m: Starting Zigbee2MQTT version 2.x.x
zigbee2mqtt | [2026-09-25 10:15:33] info: z2m: Coordinator firmware version: '{"meta":{...},"type":"zStack3x0"}'
zigbee2mqtt | [2026-09-25 10:15:34] info: z2m: Connected to MQTT server
zigbee2mqtt | [2026-09-25 10:15:34] info: z2m: Started frontend on port 8080
zigbee2mqtt | [2026-09-25 10:15:34] info: z2m: Zigbee2MQTT started!
Press Ctrl+C to stop following the log; the container keeps running. Check that the network key was generated:
sudo grep -A 2 network_key /opt/zigbee2mqtt/data/configuration.yaml
The output shows a list of 16 numbers instead of GENERATE. Copy the whole data directory to a safe place now: if you lose it, every device has to be paired again.
The web interface is available at http://your_machine_ip:8080. If UFW is active on this machine, allow it from your local network only, replacing the subnet with yours:
sudo ufw allow from 192.168.1.0/24 to any port 8080 proto tcp
WarningThe frontend has no login by default and can reconfigure the whole Zigbee network. Never expose port 8080 to the internet. To require a password, set
auth_tokenunderfrontendinconfiguration.yaml.
Step 6 - Pairing a device
New devices can only join while pairing is open. Zigbee2MQTT 2.x always starts with pairing closed, and every time you open it, it closes again automatically after a timeout, which prevents neighbors' devices from joining your network by accident.
In the web interface, click the Permit join button at the top. A countdown shows how long pairing stays open. Then put the device in pairing mode. The method depends on the device, for example holding the button for five seconds on most sensors or switching a bulb on and off several times; the device's page in the Zigbee2MQTT supported devices list describes it.
You can also open pairing over MQTT, which is handy from scripts. This opens it for 254 seconds:
mosquitto_pub -h localhost -u zigbee2mqtt -P 'your_mqtt_password' \
-t 'zigbee2mqtt/bridge/request/permit_join' -m '{"time": 254}'
Watch the log while the device joins:
sudo docker compose -f /opt/zigbee2mqtt/compose.yaml logs -f
info: z2m: Device '0x00158d0001a2b3c4' joined
info: z2m: Starting interview of '0x00158d0001a2b3c4'
info: z2m: Successfully interviewed '0x00158d0001a2b3c4', device has successfully been paired
info: z2m: Device '0x00158d0001a2b3c4' is supported, identified as: Aqara Temperature and humidity sensor (WSDCGQ11LM)
The "successfully interviewed" line is what matters. If the interview fails, keep the device awake (press its button every few seconds) and try again closer to the coordinator. When you are done, close pairing with the same button in the interface, or publish {"time": 0} to the same topic.
Step 7 - Renaming devices and reading their data
Devices join with their IEEE address as name. Give them readable names, because the name becomes part of their MQTT topics. In the web interface, open the device and click the rename (pencil) icon, or use MQTT:
mosquitto_pub -h localhost -u zigbee2mqtt -P 'your_mqtt_password' \
-t 'zigbee2mqtt/bridge/request/device/rename' \
-m '{"from": "0x00158d0001a2b3c4", "to": "livingroom_climate"}'
Subscribe to the device's topic to see its state. Sensors publish when a value changes or on their reporting interval:
mosquitto_sub -h localhost -u zigbee2mqtt -P 'your_mqtt_password' -t 'zigbee2mqtt/livingroom_climate' -v
zigbee2mqtt/livingroom_climate {"battery":100,"humidity":47.8,"linkquality":138,"temperature":21.6,"voltage":3015}
Controllable devices accept commands on <name>/set. For example, to switch on a plug named desk_plug:
mosquitto_pub -h localhost -u zigbee2mqtt -P 'your_mqtt_password' \
-t 'zigbee2mqtt/desk_plug/set' -m '{"state": "ON"}'
Step 8 - Adding the devices to Home Assistant
Because homeassistant.enabled is true, Zigbee2MQTT publishes MQTT discovery messages under homeassistant/, and Home Assistant creates entities for every device on its own. You only need to connect Home Assistant to the same broker:
- Create a broker user for Home Assistant with
mosquitto_passwd, with ACL access tozigbee2mqtt/#andhomeassistant/#if you use ACLs. - In Home Assistant, go to Settings > Devices & services > Add integration and choose MQTT.
- Enter the broker's IP address, port
1883, and the username and password you just created.
Home Assistant configures MQTT only through this integration; the old mqtt: broker: YAML keys in configuration.yaml were removed years ago and are ignored.
After a few seconds, your Zigbee devices appear under Settings > Devices & services > MQTT, with the friendly names you set in Zigbee2MQTT. Renaming a device in Zigbee2MQTT later also updates its topics, so do the renaming before you build automations.
Step 9 - Updating Zigbee2MQTT and device firmware
To update Zigbee2MQTT itself, pull the new image and recreate the container. Read the release notes first, especially for major versions:
cd /opt/zigbee2mqtt
sudo docker compose pull
sudo docker compose up -d
Many devices (IKEA, Philips Hue, Aqara, Sonoff and others) also accept firmware updates over the air (OTA). Open the OTA page in the web interface and check a device for updates; if one is available, start it from the same page. OTA transfers over Zigbee are slow and can take 30 minutes or more per device, so update one device at a time and keep battery devices awake if the device page recommends it.
Troubleshooting
Error: Failed to connect to the adapter or No such file or directory, cannot open /dev/ttyUSB0. The container cannot see the coordinator. Check that the /dev/serial/by-id/ path in compose.yaml still exists, and that adapter matches the chip of your coordinator. On Ubuntu Desktop, ModemManager sometimes grabs new serial devices; if it is installed and you do not need it, run sudo systemctl disable --now ModemManager and replug the coordinator.
Not authorized or Connection refused from MQTT. Test the credentials outside Docker with mosquitto_sub -h localhost -u zigbee2mqtt -P 'your_mqtt_password' -t 'zigbee2mqtt/#' -v. If that fails, the password or ACL on the broker is wrong.
Devices drop off the network. Zigbee is a mesh: mains-powered devices such as plugs and bulbs route traffic for battery devices. Add a few routers between the coordinator and distant sensors, move the coordinator away from USB 3 ports and Wi-Fi access points, and check the linkquality value of each device.
A device is paired but shows as "unsupported". Your Zigbee2MQTT version does not know it yet. Update Zigbee2MQTT; support for new devices is added in almost every release.
Conclusion
Zigbee2MQTT now runs in Docker next to Mosquitto, controls your Zigbee devices locally and exposes them to Home Assistant through MQTT discovery. As next steps, secure the frontend with auth_token, back up /opt/zigbee2mqtt/data on a schedule, and store sensor readings long term with Telegraf and InfluxDB.
