How to Set Up Zigbee2MQTT (Cheap Zigbee Sensors and Bulbs Without a Hub)

Zigbee is the radio inside most cheap smart devices — IKEA bulbs, Aqara sensors, Hue, Sonoff switches — and each brand wants you to buy its hub and use its cloud. Zigbee2MQTT replaces all of them: a £20 USB coordinator on your server, one container, and every device talks to your MQTT broker (tutorial 96), where Home Assistant (41) picks them up automatically. Devices from different brands mesh together and extend each other's range.
Quick reference
| Task | What |
|---|---|
| Coordinator | Sonoff Zigbee 3.0 USB Dongle Plus (ZBDongle-E or -P), Home Assistant SkyConnect, or a ConBee |
| Find it | ls -l /dev/serial/by-id/ |
| Folder | ~/docker/zigbee2mqtt/compose.yaml + data/configuration.yaml |
| Web | http://server-ip:8080 |
| Pair | web UI → Permit join, then put the device in pairing mode |
| Home Assistant | devices appear by themselves via MQTT discovery |
1. Plug in the stick
Use a short USB extension cable — a coordinator plugged straight into a server's USB 3 port suffers interference. Then:
ls -l /dev/serial/by-id/
Something like usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_xxxx-if00-port0. Use that full by-id path; /dev/ttyUSB0 can change.
2. The compose file
mkdir -p ~/docker/zigbee2mqtt/data && cd ~/docker/zigbee2mqtt
nano compose.yaml
services:
zigbee2mqtt:
image: koenkk/zigbee2mqtt:latest
container_name: zigbee2mqtt
ports:
- "8080:8080"
environment:
TZ: Europe/London
volumes:
- ./data:/app/data
- /run/udev:/run/udev:ro
devices:
- /dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_xxxx-if00-port0:/dev/ttyACM0
restart: unless-stopped
3. The config file
nano data/configuration.yaml
homeassistant:
enabled: true
mqtt:
base_topic: zigbee2mqtt
server: mqtt://192.168.1.50:1883
user: zigbee2mqtt
password: your-mosquitto-password
serial:
port: /dev/ttyACM0
adapter: ember # ember for ZBDongle-E / SkyConnect; zstack for ZBDongle-P; deconz for ConBee
frontend:
enabled: true
port: 8080
advanced:
channel: 15
network_key: GENERATE
log_level: info
permit_join: false
Create the zigbee2mqtt user in Mosquitto first (tutorial 96). channel: 15 or 25 avoids the busiest Wi-Fi channels; network_key: GENERATE makes a unique key on first start.
docker compose up -d
docker compose logs -f
"Zigbee2MQTT started" means the coordinator is talking. First start on a new stick may update its firmware; let it.
4. Pair a device
Open http://192.168.1.50:8080 → Permit join (All). Put the device in pairing mode — usually hold its button for five seconds, or switch a bulb off and on six times (IKEA) — and watch it appear with a name like 0x00158d0001234567. Click the pencil to rename it kitchen_motion. Turn Permit join off when done.
5. Home Assistant
With homeassistant: enabled: true and the MQTT integration set up, every paired device appears under Settings → Devices within seconds, with all its entities: motion, temperature, battery, illuminance, the bulb's brightness and colour. Automations work exactly as with any other device.
6. Build a good mesh
Battery devices (sensors) only talk; mains-powered devices (bulbs, plugs, switches) also route. A couple of smart plugs or bulbs between the server and the far end of the house carry the signal. Pair routers first, close to the coordinator, then move them. The Map tab in the web UI shows who's talking to whom; a sensor with a poor link needs a router nearby.
Troubleshooting
"Failed to start" / "Error: Cannot connect". Wrong adapter for the stick, or the device path. Try the other adapter value; confirm the by-id path.
Devices pair then drop off. Interference. Extension cable, move the stick away from USB 3 and Wi-Fi routers, change channel.
Nothing in Home Assistant. MQTT integration not set up, or homeassistant: enabled missing. mosquitto_sub -t 'homeassistant/#' -v shows the discovery messages if Zigbee2MQTT is sending them.
Lost all devices after moving the stick to another USB port. The by-id path changed. Update the compose file; devices stay paired (the network key is in data/). Back up that folder.
Wrapping up
A USB coordinator on an extension cable, one container, a config pointing at your MQTT broker, Permit join. Every Zigbee device in the house joins one mesh, Home Assistant sees them all, and the drawer of brand hubs goes to the charity shop.
