all systems self-hosted

~/selfhostjournal $

← All articles

,

Zigbee2MQTT in Docker: my configuration, and why sensors keep going offline

4–6 minutes

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 separate secret.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

  1. Check the last seen time of each sensor that goes offline, to separate the timeout from real drop-outs.
  2. Raise the availability timeout for door and window contacts. They don’t need to report every day.
  3. Keep bulbs that act as routers powered all the time and switch them through Zigbee instead of the wall switch.
  4. Add a dedicated, always-on router (a USB router stick or a good smart plug) near the sensors that drop most often.
  5. 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.

Everything here worked in my setup at the time of writing. Software changes, and your setup is different: back up first, test carefully, and use the steps at your own risk. Details in the legal notice.