Picture moving every bulb off its vendor hub, opening Home Assistant and finding an empty room. The bulbs might have joined the new Zigbee network while discovery failed farther along; resetting them all again would only add work. That is the failure I would plan around before clearing the hub drawer. Zigbee2MQTT can connect supported models through your own coordinator and MQTT broker, but features, firmware handling and pairing remain model-specific. Start with one device so there is still a known route back if it refuses to cooperate.

First, draw the path

Follow a state change through the four parts of the setup. The coordinator handles the Zigbee network, Zigbee2MQTT talks to it and publishes data and discovery messages to the MQTT broker, and Home Assistant’s MQTT integration creates devices and entities from those messages. If something disappears, that path gives you separate checkpoints to inspect. The Zigbee2MQTT integration guide[1] describes the connections.

Look up the exact model on the supported devices list[2] and check the features it exposes before resetting it for the new network. Keep the bridge available while you pair and verify its devices. With the coordinator, broker, Home Assistant and automations running locally, control can continue without internet; test that on your deployment, including any device features that still depend on a cloud service.

Choose the coordinator and host

Use an adapter from Zigbee2MQTT's current supported-adapter list[3] and read its model page for firmware and serial.adapter instructions. A USB coordinator keeps the serial link on the host; a wired Ethernet coordinator may give you more placement options. The project's adapter guide recommends Ethernet over Wi-Fi for a remote serial adapter because packet loss and latency can disturb the connection. Avoid buying on an old chipset comparison or a universal range claim.

If the coordinator is USB, move it away from USB 3.0 ports and other 2.4 GHz sources when practical. Zigbee2MQTT's range guide[4] recommends an extension cable as a troubleshooting measure; its useful length depends on your setup. Record the host's /dev/serial/by-id/ path before installation. That stable path is safer than guessing whether the device will be /dev/ttyACM0 after a restart.

You also need an MQTT broker. On Home Assistant OS, the official Mosquitto Broker app is the documented starting point: Home Assistant’s MQTT integration[5] can install it and generate credentials for its own connection. Create an additional login for Zigbee2MQTT in the app’s options rather than trying to extract Home Assistant’s private credentials. A separate Docker installation can use another compatible broker that you administer. Keep access authenticated and limited to the intended clients, then confirm that Home Assistant’s MQTT integration connects under Settings → Devices & services.

Install Zigbee2MQTT

For Home Assistant OS, use the Zigbee2MQTT app instructions[6]: add the project repository, install the stable app, select the actual adapter path and type, and complete onboarding. The project still uses “add-on” in some documentation. Read the app’s own options and data-path guidance; those options can override the YAML file. Home Assistant Container has no app store, so use a separate deployment such as the Docker route below. Do not choose an old Supervised tutorial for a new installation: Home Assistant ended support for that installation method with 2025.12[7], even though an older Zigbee2MQTT page still mentions it.

For a standalone Linux Docker host, this follows the project's Compose example[8]. Replace the device path and timezone. Keep the broker address reachable from inside this container; localhost would name the Zigbee2MQTT container itself.

services:
  zigbee2mqtt:
    image: ghcr.io/koenkk/zigbee2mqtt:2.14.2
    restart: unless-stopped
    volumes:
      - ./zigbee2mqtt-data:/app/data
      - /run/udev:/run/udev:ro
    ports:
      - "8080:8080"
    environment:
      - TZ=America/Chicago
    devices:
      - /dev/serial/by-id/YOUR_ADAPTER_ID:/dev/ttyACM0

The host side of devices is your actual /dev/serial/by-id/ entry; /dev/ttyACM0 is the path inside this container. The image is pinned to the checked stable release, 2.14.2. The port mapping publishes 8080 on the host, so restrict access to a trusted administration network before starting it; onboarding is not a public setup page. Start with docker compose up -d zigbee2mqtt. If no configuration.yaml exists, open port 8080 on that host and complete onboarding. Let it create the new network configuration rather than replacing the generated file with a snippet afterward.

Before migrating an existing network, stop Zigbee2MQTT and save the entire data directory, its configuration and any supported coordinator backup. Keep the original network key and identifiers with that backup. The manual example below is an alternative for a new network, not a migration recipe.

Connect the four pieces

If you choose a manual file instead of onboarding for a new network, place this configuration.yaml in the mapped data directory before the first start. It follows the checked release’s configuration format, version: 5; that is the file-format version, not the MQTT protocol version. The example uses a Texas Instruments zStack adapter and a broker at the documentation address 192.0.2.10. Substitute your actual adapter type, reachable broker and credentials. The Home Assistant app has its own configuration route and can supply MQTT options there instead.

version: 5
homeassistant:
  enabled: true
mqtt:
  base_topic: zigbee2mqtt
  server: mqtt://192.0.2.10:1883
  user: mqtt_user
  password: CHANGE_ME
serial:
  port: /dev/ttyACM0
  adapter: zstack
advanced:
  network_key: GENERATE
  pan_id: GENERATE
  ext_pan_id: GENERATE
  enable_external_js: false
frontend:
  enabled: true

For this new network, GENERATE asks Zigbee2MQTT to create and save the key and identifiers at startup. Do not paste those settings over a network that already works: changing the network key requires re-pairing its devices. Keep the generated values and the rest of the data directory private and backed up. The network configuration reference[9] explains generation and the migration consequences; enable_external_js: false follows the checked release’s new-install defaults.

The adapter configuration guide[10] says to choose the adapter value from your hardware's page; zstack is only for the example adapter. The Home Assistant integration guide[11] uses homeassistant.enabled: true for MQTT discovery. Restart Zigbee2MQTT after changing that setting. Home Assistant's MQTT discovery is normally enabled, but its MQTT options[12] let you check the discovery switch and topic prefix if devices do not appear.

Read the Zigbee2MQTT log until it reports a successful startup, then open its frontend. If it cannot open the serial port, check the actual host path, container device mapping, permissions and adapter type. Make sure another Zigbee application is not already using the coordinator; it can serve only one Zigbee2MQTT network and cannot be shared with another controller application. If it cannot connect to MQTT, verify the broker address and login from the Zigbee2MQTT environment, not merely from the Home Assistant host. You can listen to zigbee2mqtt/# in Home Assistant's MQTT integration to distinguish broker traffic from discovery problems.

Pair one supported device

Read that model's pairing instructions on Zigbee2MQTT's device page, then put it into pairing mode. In the frontend, Permit join opens the network for a limited time; the pairing guide[13] says the frontend's interval is 254 seconds. You can close it sooner. If you need a 60-second window through MQTT, the documented request is:

mosquitto_pub -h YOUR_BROKER -u mqtt_user -P 'YOUR_PASSWORD' \
  -t zigbee2mqtt/bridge/request/permit_join -m '{"time":60}'

Check the matching zigbee2mqtt/bridge/response/permit_join response and the Zigbee2MQTT interview log. A successful interview identifies the device; rename it to a useful room-based name in the frontend. With Zigbee2MQTT discovery enabled and Home Assistant's MQTT integration connected, look for the device and its supported entities under Home Assistant's MQTT integration. A temperature sensor may expose temperature and battery; another model may expose different data. Test one state change before moving the rest of the devices.

When the path breaks

No interview: Check that joining is permitted, reset the device using its model-specific instructions, and try near the coordinator or a suitable router. Zigbee2MQTT's pairing guide[13] and FAQ[14] cover the common failure paths. Do not change a working network channel based on a generic Wi-Fi-channel rule; inspect local interference and the project's channel guidance first.

Interview succeeds, but Home Assistant is empty: Follow the messages instead of resetting the device again. Confirm homeassistant.enabled: true, the broker connection and Home Assistant’s discovery options. With the default discovery prefix, listen under homeassistant/#; if you changed that prefix, use the configured value on both sides. Compare those discovery messages with the device’s data under zigbee2mqtt/#, then inspect Home Assistant’s MQTT device registry. A successful Zigbee interview cannot repair a broker or discovery mismatch.

A device is listed as unsupported: Check its exact model and exposed features on the supported-devices site. The Zigbee2MQTT documentation[15] explains an advanced converter route if you want to take that on. I would keep the vendor bridge while assessing that work rather than try an unstable build simply to clear one device from the drawer.

Intermittent devices: The coordinator, powered routers and battery end devices have different roles. A powered device is not automatically a router, so check its model and the network map guidance[14] before placing new plugs. Zigbee2MQTT's range guide[4] covers USB placement, interference and routers. Change one variable at a time and observe whether a problem device reports reliably; allow the observed reporting behavior to guide your next change.

Keep the new network maintainable

Protect the broker login, generated network key and Zigbee2MQTT frontend using the project’s security guidance[16]. Keep a private copy of the complete data directory and read the adapter backup limits[3] before replacing hardware: coordinator backup is supported for zStack and EmberZNet, not every adapter family. Record the image version with the backup so recovery does not begin with an accidental upgrade.

Keep the network permissions matched to the actual path. Home Assistant and Zigbee2MQTT each need to reach the MQTT broker; Zigbee2MQTT needs its serial or network connection to the coordinator. Home Assistant does not need direct access to the coordinator merely to consume MQTT discovery. Use the VLAN planning guide to allow those required connections rather than opening the whole segment. I would preserve a known-working message path before adding another layer of isolation.

Once the first device pairs, publishes a state change and appears in Home Assistant, move the others one model at a time. A stubborn bulb is much easier to investigate when the broker, coordinator and discovery path are already known to work. Let that sequence decide when each old hub can leave the drawer.