Tutorials

Zigbee2MQTT: Connect Zigbee Devices to Any MQTT Broker

Run Zigbee2MQTT against any MQTT broker: configuration.yaml, TLS and credentials, Docker, the bridge and device topics, and how to test every step.

Updated · 8 min read

Zigbee2MQTT: Connect Zigbee Devices to Any MQTT Broker

Zigbee2MQTT turns a USB Zigbee coordinator into an MQTT gateway: every sensor, bulb and switch paired to it shows up as a JSON message on a topic, and every topic ending in /set becomes a command. It does not ship a broker — you point it at one in configuration.yaml, and any standards-compliant MQTT broker works: a local Mosquitto, the Home Assistant add-on, or a managed broker in the cloud.

This tutorial configures Zigbee2MQTT (2.x) against an external broker, explains the topic layout, and shows how to verify the connection and control a device from the command line.

How the pieces fit

Data flow
Zigbee device ──radio──▶ Coordinator (USB) ──serial──▶ Zigbee2MQTT ──MQTT──▶ Broker ──▶ Home Assistant / Node-RED / your app

Zigbee2MQTT is just another MQTT client. That has two practical consequences: the broker can live anywhere reachable over the network, and anything else that speaks MQTT — a dashboard, a script, the online client — sees exactly the same messages your home-automation software sees.

Minimal configuration

Zigbee2MQTT reads data/configuration.yaml. The MQTT section is the part that connects it to your broker:

data/configuration.yaml
mqtt:
  base_topic: testmqtt/z2m          # default is "zigbee2mqtt"
  server: mqtts://broker.connectmq.com:8883
  user: '!secret user'
  password: '!secret password'
  client_id: z2m-living-room
  keepalive: 60
  version: 5                        # 3, 4 (3.1.1) or 5

serial:
  port: /dev/ttyACM0
  adapter: ember                    # zstack, ember, deconz, zigate, zboss

frontend:
  enabled: true
  port: 8080

homeassistant:
  enabled: false                    # true if Home Assistant should auto-discover devices

advanced:
  log_level: info
data/secret.yaml
user: username
password: password

Notes on the important keys:

  • server — mqtt://host:1883 for plain TCP, mqtts://host:8883 for TLS. WebSocket URLs (wss://host:8084/mqtt) also work because Zigbee2MQTT uses MQTT.js under the hood, but TCP is the normal choice for a gateway.
  • base_topic — the root of everything Zigbee2MQTT publishes. Keep the default zigbee2mqtt on a private broker; on a shared test broker use a unique root like the one above so you do not collide with other users.
  • client_id — must be unique on the broker. Two instances with the same ID will kick each other off in a loop; see client ID best practices.
  • ca, key, cert — only needed for a private CA or mutual TLS. A broker with a publicly trusted certificate works with mqtts:// alone. Never "fix" certificate errors with reject_unauthorized: false in production.
  • !secret — keeps credentials in secret.yaml so you can share or commit configuration.yaml safely.

Every option can also be set via environment variables, which is convenient in containers: ZIGBEE2MQTT_CONFIG_MQTT_SERVER, ZIGBEE2MQTT_CONFIG_MQTT_USER, ZIGBEE2MQTT_CONFIG_MQTT_PASSWORD, and so on.

Running it with Docker

docker-compose.yml
services:
  zigbee2mqtt:
    image: ghcr.io/koenkk/zigbee2mqtt
    restart: unless-stopped
    volumes:
      - ./data:/app/data
      - /run/udev:/run/udev:ro
    ports:
      - "8080:8080"
    environment:
      - TZ=Europe/Berlin
    devices:
      - /dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus-if00-port0:/dev/ttyACM0

Map the coordinator by its /dev/serial/by-id/... path: /dev/ttyUSB0 can change after a reboot or when another USB device is plugged in. If you run a local broker in the same Compose file, refer to it by service name (mqtt://mosquitto:1883), not localhost — inside a container localhost is the container itself. The Docker Compose stack guide covers that setup.

Start it and watch the log:

Start and follow logs
docker compose up -d
docker compose logs -f zigbee2mqtt

A healthy start logs Connecting to MQTT server at mqtts://broker.connectmq.com:8883 followed by Connected to MQTT server. If you see MQTT error: Connection refused: Not authorized or MQTT failed to connect, exiting..., check credentials and the port/scheme pair first — the Zigbee2MQTT errors guide walks through each message.

The topic layout

TopicDirectionPayload
<base>/bridge/stateZ2M → broker (retained){"state":"online"} / {"state":"offline"} (Last Will)
<base>/bridge/infoZ2M → broker (retained)Version, coordinator, config
<base>/bridge/devicesZ2M → broker (retained)All paired devices with capabilities
<base>/bridge/eventZ2M → brokerDevice joined, interview finished, left
<base>/bridge/request/...You → Z2MAdmin requests (permit join, rename, remove)
<base>/bridge/response/...Z2M → youResult of each request
<base>/<friendly_name>Z2M → brokerDevice state as JSON
<base>/<friendly_name>/setYou → Z2MCommands as JSON
<base>/<friendly_name>/getYou → Z2MAsk the device to report a value
<base>/<friendly_name>/availabilityZ2M → broker (retained){"state":"online"} when availability is enabled

New devices are named by their IEEE address (0x00158d0001a2b3c4). Rename them to something meaningful; a slash in the name creates a hierarchy, so living_room/ceiling becomes testmqtt/z2m/living_room/ceiling. That is the easiest way to get a clean smart home topic tree.

bridge/state, bridge/info and bridge/devices are retained messages, so any client that subscribes later immediately learns whether the bridge is up and which devices exist.

Test every step from the command line

1. Is the bridge connected? Subscribe to the bridge state; the retained value arrives instantly:

Bridge state
mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -u "$MQTT_USER" -P "$MQTT_PASS" -t 'testmqtt/z2m/bridge/state' -v
# testmqtt/z2m/bridge/state {"state":"online"}

2. Watch all traffic while you press a button or open a door sensor:

Everything under the base topic
mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -u "$MQTT_USER" -P "$MQTT_PASS" -t 'testmqtt/z2m/#' -v

3. Pair a device by opening the network for a limited time (Zigbee2MQTT 2.x requires time; 0 closes it again):

Permit join for 120 seconds
mosquitto_pub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -u "$MQTT_USER" -P "$MQTT_PASS" \
  -t 'testmqtt/z2m/bridge/request/permit_join' -m '{"time": 120}'

The answer arrives on bridge/response/permit_join, and bridge/event reports device_joined and device_interview as the device pairs.

4. Rename and control it:

Rename, then toggle
mosquitto_pub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -u "$MQTT_USER" -P "$MQTT_PASS" -t 'testmqtt/z2m/bridge/request/device/rename' \
  -m '{"from": "0x00158d0001a2b3c4", "to": "living_room/ceiling"}'

mosquitto_pub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -u "$MQTT_USER" -P "$MQTT_PASS" -t 'testmqtt/z2m/living_room/ceiling/set' \
  -m '{"state": "TOGGLE", "brightness": 180, "transition": 1}'

The bulb's new state appears on testmqtt/z2m/living_room/ceiling within a second. If a command does nothing, check bridge/logging — Zigbee2MQTT logs rejected payloads there.

You can run the same steps in the browser: open the online MQTT client, subscribe to testmqtt/z2m/# and publish to the /set topics from the publish panel.

Watch your Zigbee2MQTT topics live

Integrating with Home Assistant

Set homeassistant: enabled: true and Zigbee2MQTT publishes discovery messages under homeassistant/.... Home Assistant's MQTT integration must point at the same broker; devices then appear automatically with all their entities. If you changed base_topic, nothing else needs to change — the discovery payloads include the right state and command topics. See Home Assistant and MQTT for the broker side.

Production checklist

  1. Give Zigbee2MQTT its own broker user and restrict it to <base>/# plus homeassistant/# if discovery is on.
  2. Use TLS (mqtts://) whenever the broker is not on the same host.
  3. Enable availability so offline devices are reported instead of silently stale.
  4. Keep permit_join closed except while pairing.
  5. Back up the whole data/ directory — it contains the network key and the coordinator backup, not just the config.

Zigbee2MQTT is the simplest way to free Zigbee devices from vendor hubs: one YAML block points it at any broker, and from then on every device is just a topic. Verify the bridge state first, watch the traffic while you pair, and control devices with plain mosquitto_pub before you build automations on top.

Frequently asked questions

Does Zigbee2MQTT include its own MQTT broker?

No. Zigbee2MQTT is an MQTT client: it translates Zigbee radio traffic into MQTT messages and needs an external broker, such as Mosquitto, the Home Assistant Mosquitto add-on or a managed broker.

Can Zigbee2MQTT connect to a broker over TLS?

Yes. Set mqtt.server to mqtts://host:8883. Brokers with a publicly trusted certificate need nothing else; for a private CA add the ca option, and for mutual TLS add key and cert.

How do I control a Zigbee device through MQTT?

Publish JSON to <base_topic>/<friendly_name>/set, for example {"state": "TOGGLE"} or {"brightness": 128}. The device's new state is published on <base_topic>/<friendly_name>.