In short
Problem: Run Zigbee2MQTT in Docker for about 80 devices, and battery sensors that keep showing as offline.
Solution: A small Docker Compose file, a clean configuration.yaml, and understanding why “offline” often doesn’t mean “gone”.
Tested with: Zigbee2MQTT 2.14, TI CC2652 USB coordinator (Z-Stack), Home Assistant, Mosquitto
My Zigbee network has grown to around 80 devices: radiator thermostats, temperature and motion sensors, door contacts, plugs and a lot of smart bulbs. Zigbee2MQTT connects all of them to Home Assistant through MQTT. Here is the setup I use, and the one problem that kept bugging me.
1. Docker Compose
Zigbee2MQTT runs as a single container. The USB stick is passed in by its by-id path, so it still works when USB devices are enumerated in a different order after a reboot.
services:
zigbee2mqtt:
container_name: zigbee2mqtt
image: ghcr.io/koenkk/zigbee2mqtt:latest
restart: unless-stopped
environment:
TZ: Europe/Berlin
ZIGBEE2MQTT_CONFIG_FRONTEND_ENABLED: "true"
ZIGBEE2MQTT_CONFIG_FRONTEND_PORT: "8080"
# the frontend password lives in .env, not in this file
ZIGBEE2MQTT_CONFIG_FRONTEND_AUTH_TOKEN: ${Z2M_AUTH_TOKEN:?set Z2M_AUTH_TOKEN in .env}
volumes:
- ./:/app/data
- /run/udev:/run/udev:ro
devices:
# find yours with: ls -l /dev/serial/by-id/
- /dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0:/dev/ttyUSB0
ports:
# frontend only on the host itself
- "127.0.0.1:8080:8080"
Tip: don’t publish the frontend port to your whole network. Anyone who reaches it can pair or remove devices. I bind it to 127.0.0.1 and put it behind a reverse proxy with single sign-on.
2. configuration.yaml
This is my configuration with all secrets removed. On a new installation, GENERATE lets Zigbee2MQTT create a random network key and PAN IDs on the first start. Never copy these values from someone else’s example.
version: 5
mqtt:
base_topic: zigbee2mqtt
server: mqtt://mqtt-broker:1883
user: zigbee2mqtt
password: '!secret mqtt_password'
serial:
port: /dev/ttyUSB0
adapter: zstack
baudrate: 115200
rtscts: false
advanced:
channel: 15
network_key: GENERATE
pan_id: GENERATE
ext_pan_id: GENERATE
transmit_power: 20
adapter_delay: 100
last_seen: ISO_8601
log_level: error
availability:
enabled: true
homeassistant:
enabled: true
frontend:
enabled: true
port: 8080
A few notes on the settings:
channel: pick it once and don’t change it later. Changing the channel means re-pairing many devices. Zigbee channels 15, 20 and 25 overlap least with the common Wi-Fi channels 1, 6 and 11.last_seen: ISO_8601: adds a timestamp to every device. You need this to understand the offline problem below.availability: marks devices as online or offline in Home Assistant. Useful, but it causes most of the confusion described next.password: '!secret mqtt_password'reads the value from a separatesecret.yaml, so the configuration can be shared or backed up without the password.
Watch out: back up the whole data folder, especially configuration.yaml, database.db and coordinator_backup.json. Without the network key and the coordinator backup, a new stick means re-pairing every device.
3. Sensors that keep leaving the network
This is the part that kept bugging me. Every few days some battery sensors, mostly door and window contacts, show up as offline in Zigbee2MQTT and Home Assistant. So far the fix has always been the same: open and close the door once, and the sensor is back online immediately. No re-pairing, no battery change.
That a simple open and close brings them back is actually a good clue. There are two likely explanations, and they can both happen in the same network.
Reason 1: the sensor never left, it was just quiet
Battery devices sleep most of the time and only send something when their state changes or at a long check-in interval. Zigbee2MQTT cannot ask them “are you still there?”. With availability turned on, it simply waits: if a battery device has sent nothing for 25 hours (the default), it is marked offline. A door that nobody opened for a day looks exactly like a dead sensor. Opening it sends a message, and the sensor is “back”.
You can recognise this case by the last seen time: if the sensor went offline almost exactly 25 hours after it was last seen, it is the timeout and not a real problem. The fix is a longer timeout for these devices:
availability:
enabled: true
active:
timeout: 10 # minutes, mains-powered devices (they can be pinged)
passive:
timeout: 1500 # minutes, battery devices (default = 25 hours)
# or only for single devices, in the devices section:
devices:
'0x00158d0001234567':
friendly_name: Front door
availability:
timeout: 4320 # 3 days
Reason 2: the sensor lost its parent router
Battery devices don’t talk to the coordinator directly. They attach to a nearby router, which is any mains-powered device such as a plug or a smart bulb. If that router disappears, the sensor is orphaned. It only notices when it tries to send something, and then looks for a new parent. That is why opening the door “repairs” it: the open event forces the sensor to rejoin.
Typical reasons a router disappears or drops its children:
- Smart bulbs switched off at the wall. Every bulb is a router. Switch it off and every sensor attached to it is orphaned. This is the most common cause.
- Weak routers. Many cheap bulbs and plugs can only keep a few children, or drop them after a firmware glitch.
- Aqara/Xiaomi sensors. They don’t follow the Zigbee standard completely and often only stay connected to certain routers. With other brands as parents they drop off regularly.
What I’m doing about it
- Check the last seen time of each sensor that goes offline, to separate the timeout from real drop-outs.
- Raise the availability timeout for door and window contacts. They don’t need to report every day.
- Keep bulbs that act as routers powered all the time and switch them through Zigbee instead of the wall switch.
- Add a dedicated, always-on router (a USB router stick or a good smart plug) near the sensors that drop most often.
- Look at the network map in the Zigbee2MQTT frontend to see which router each problem sensor hangs on.
Tip: don’t re-pair a sensor just because it shows as offline. Trigger it first (open the door, press its button, breathe on a temperature sensor). If it comes back, it was never really gone.
What I learned
- “Offline” in Zigbee2MQTT often just means “hasn’t said anything for 25 hours”.
- The routers decide how stable the network is, not the coordinator. Bulbs on wall switches are bad routers.
- Back up the network key and coordinator backup. They are the difference between swapping a stick in minutes and re-pairing 80 devices.
I’ll update this article once I know which of the two reasons applies to my sensors.