Zigbee Mesh Optimization: From 20 to 100 Devices Without the Dropouts
I have run a Home Assistant instance in a three-floor brick house for four years. The Zigbee network grew from a handful of sensors to just over a hundred devices. Somewhere between 40 and 60 devices, it stopped being a friendly star and became a fragile web: devices that had been rock-solid for months started going offline at random hours. This is how I learned to read a mesh like a network engineer instead of a hobbyist, and how I took the drop rate from "a few every week" to "none in months."
1. Know the Three Roles Before You Touch Anything
A Zigbee network is not a flat list of devices. It is a three-tier hierarchy, and every device has exactly one job.
Coordinator — the single heart. Mine is a Sonoff Zigbee 3.0 USB dongle Plus (CC2652P). It holds the network key, assigns short addresses, and is the one node that must never be moved or swapped casually. If it changes, every device has to be re-paired.
Router — the relay. Mains-powered: plugs, smart bulbs, wall switches, thermostats. Always awake, it forwards traffic from its children toward the coordinator. A plug at the far end of a room is worth ten battery sensors for mesh stability.
End device — the leaf. Battery-powered: door/window sensors, temperature sensors, some remotes. They sleep, wake on an interval or an event, and talk to exactly one parent. They never relay for anyone else.
Here is the decision table I keep in my ops notes. Before I buy anything, I decide which bucket it falls into:
| Device type | Power source | Mesh role | What I do with it |
|---|---|---|---|
| Coordinator (USB stick) | USB | Heart | Mount it, never move it, back up its network key |
| Smart plug / bulb / switch | Mains | Router | Place it to fill a coverage gap |
| Door / window sensor | Coin cell | End device | Pair it, then verify it is within 2 hops |
| Temp / humidity sensor | AA/AAA | End device | Treat as a leaf; its parent matters more than the device |
| Zigbee 3.0 hub bridge | Mains | Coordinator (its own) | Treat as a separate network, never mix |
The most common mistake I see is people treating every device as equal and then wondering why the far corner of the house is flaky. It is not flaky — it is two or three relays short of healthy.
2. Why a Mesh Degrades as You Scale
Zigbee is a mesh, so adding devices should add paths and make it stronger. It does the opposite, because the path to the coordinator gets longer, and end devices do not choose the best path — they choose the first one they can hear.
Two numbers govern everything:
- Hop count. Every device talks to a parent, every parent to a grandparent, until the coordinator. One hop is a direct child of the coordinator. Three hops is where latency and retransmissions start to hurt. Four hops is a device waiting for permission to fail.
- RSSI (received signal strength). Reported per link in dBm. Below -85 dBm is dead. -80 to -85 is flaky. -70 to -80 is usable but fragile. -60 to -70 is where I want every leaf to live.
A healthy mesh, in my book: every end device within 2 hops of a router, that router within 1-2 hops of the coordinator, no link below -75 dBm, and the coordinator talking directly to most routers — a star core, with a few routers daisy-chained only where a wall forces it. When hops climb and RSSI slides, you get what I had: devices "fine" in the morning and gone at 3am.
3. My Setup and the Numbers
My house is 3 floors, brick interior walls, and a steel-reinforced floor slab that eats 2.4GHz for breakfast. After two rounds of rework I settled on 11 routers plus the coordinator, carrying about 100 devices total. The coordinator sits central on the middle floor, on a USB extension cable so it is not buried behind a metal rack.
A rough floor-plan sketch of the router core:
Floor 2 (top): R-09 --- R-10 --- R-11 (bedroom wing)
\ /
Floor 1 (mid): R-04 -- COORD -- R-05 -- R-06
/ \ \ \
/ R-03 --- R-07 --- R-08
Floor 0 (base): R-01 --- R-02 (garage / utility)
Before the top-floor ring, the north bedroom was a 4-hop chain: sensor -> R-09 -> R-06 -> R-04 -> coordinator. Now it is sensor -> R-10 -> R-05 -> coordinator, and R-10 connects straight back to R-06 as a second path.
The resulting hop matrix for the worst zone (north bedroom, 14 end devices):
| Path | Hops before | RSSI before | Hops after | RSSI after |
|---|---|---|---|---|
| Bedroom door sensor -> coordinator | 4 | -86 dBm | 2 | -69 dBm |
| Window sensor east -> coordinator | 3 | -81 dBm | 2 | -71 dBm |
| Temp sensor nightstand -> coordinator | 4 | -88 dBm | 2 | -66 dBm |
| Closet sensor -> coordinator | 3 | -79 dBm | 2 | -70 dBm |
Across the whole network, the RSSI distribution after the fix was ~78% of links at -70 dBm or better, ~19% between -70 and -75, and only ~3% below -75. That bottom 3% is the list I keep watching.
4. Five Optimization Points
Point 1: Router placement — star-plus-ring, not a chain
The worst thing you can build is a chain: coordinator -> R-A -> R-B -> R-C -> sensor. If R-B dies, everything beyond it is orphaned. I rebuild toward a star core with a ring on the edge — most routers within 1 hop of the coordinator, the far ones connected two ways so a single failure does not strand a wing. A smart plug at the far end of a room is the cheapest router you can buy.
Point 2: RSSI thresholds — per zone, not one global number
A single "-75 dBm is bad" rule does not survive contact with a brick wall. In my open-plan living area, -80 dBm is fine because there is no interference. In the basement utility room, -68 dBm is a warning because a freezer compressor sits next to it. I keep thresholds per zone in a small map and alert on the zone number, not a global constant.
Point 3: Hop count — 3 is the ceiling, 2 is the comfort zone
I treat 3 hops as the hard ceiling and 2 hops as the comfort zone. Any end device that reports 3 hops gets a router inserted into its path or gets moved. Any device that jumps from 2 to 4 hops is a red flag that its parent router is about to fail — that is Incident 2 below.
Point 4: Channel planning — avoiding the WiFi band is the cheapest win
2.4GHz Zigbee and 2.4GHz WiFi share spectrum. My WiFi runs on channels 1, 6, and 11; Zigbee lives on 25 — in the upper band, clear of WiFi 11's edge. The scan took five minutes and eliminated a whole class of intermittent drops. If you only do one thing from this article, do this one: scan channels 11-26, pick the quietest that does not sit under a WiFi channel, and lock it.
# Zigbee2MQTT configuration.yaml
advanced:
channel: 25
pan_id: 0x1a62
network_key: !secret zigbee_network_key
Point 5: OTA update order — batch, staged, off-peak
Zigbee firmware updates (OTA) run over the mesh and each one loads the coordinator hard. I do no more than 4 devices per batch, run batches back-to-back but never overlapping, and schedule them after 1am when the network is quiet and nobody is triggering motion sensors. A single large OTA mid-day is how I learned Incident 3.
5. Using Zigbee2MQTT Graph View to Read the Topology
The Zigbee2MQTT graph view (the "Network map" page, or the devices endpoint with ?graph=) is the best diagnostic tool I have. It draws parent-child relationships so I can see a chain instantly. I pair it with an MQTT query for the per-device details the graph hides:
mosquitto_pub -h localhost -t zigbee2mqtt/bridge/request/device/info -m '{"id": "0x00158d0001a2b3c4"}'
mosquitto_sub -h localhost -t zigbee2mqtt/bridge/response/device/info -v
The response gives me linkquality (RSSI) and the endpoint list. To see the whole mesh at once, I subscribe to every device topic and pull linkquality:
The graph is where I spotted the problem in Incident 2 — a router that had quietly re-parented two hops further away, dragging five end devices with it. The visual showed it before the MQTT data did, because the numbers were still "in range" but the route was bad.
6. Four Real Incidents
Incident 1: dozens of devices dropped at 3am together
Woke up to 40+ devices offline. The tell was that they all came back within minutes and all at once. Cause: a router had rebooted (a plug was power-cycled by a firmware update), and every end device on it had the same poll interval, so they all tried to rejoin simultaneously — a rejoin storm. Fix: I staggered poll intervals (300s / 360s / 420s) and moved a few leaves to a different parent. No repeat since.
Incident 2: a router went 2 hops to 4 hops and 5 end devices fell off
The graph showed a hallway router had re-parented itself two hops further away, and its five children came along. RSSI looked fine because the children were close to the router — the problem was the router-to-coordinator path. Fix: a new smart plug between it and the core, and an alert on "router hop count increased by 2+."
Incident 3: one OTA pegged the coordinator CPU for 8 minutes
I pushed a 12-device OTA batch in one go. The coordinator stalled for 8 minutes and a dozen other devices dropped because they could not get a response. Fix: 4 devices per batch, sequential, and I now watch coordinator CPU before I start.
Incident 4: migrating 6 Hue bulbs off a Zigbee 1.2 bridge failed
I tried to move six Philips Hue bulbs from an old Hue bridge onto my Zigbee 3.0 coordinator. Three paired fine; three would not join no matter what. Cause: those bulbs were on an old firmware that spoke Zigbee 1.2, and the re-pairing handshake with a 3.0 network was flaky. Fix: I updated the bulbs' firmware through the Hue bridge first, then re-paired — all six joined on the second attempt.
7. Zigbee 1.2 vs 3.0 Migration Pitfalls
Migrating off a Zigbee 1.2 hub (an old Hue bridge or a vendor gateway) onto a 3.0 coordinator has three traps I have hit:
- Network key. A 1.2 hub generated its key and may keep it secret. When I moved devices, the new coordinator generated a fresh key, and old devices that "remembered" the old network had to be factory-reset, not just re-paired. Factory reset, then pair.
- Coordinator swap. Moving the coordinator itself (not the devices) is a full re-pair for every device. I back up the network key to a secret file first and keep the old coordinator untouched until the new mesh is confirmed healthy.
- Re-pairing order. Pair routers first, then end devices. If you pair a battery sensor before its future parent router is on the mesh, it grabs a far-away parent and stays there until you force it off and on again.
A safe migration sequence for me: back up key -> add new coordinator -> pair all routers -> power-cycle and re-pair end devices -> verify hop matrix -> only then retire the old hub.
8. Health-Monitoring Dashboard: What to Watch
My Home Assistant dashboard has a "Zigbee health" panel. I keep five cards:
- Worst RSSI per zone — a list of the bottom 5 links, updated on MQTT.
- Hop count histogram — how many devices at 1, 2, 3, 4+ hops. A count at 3+ is my action trigger.
- Offline count — with an alert if it exceeds 3 for more than 5 minutes.
- Coordinator CPU — from Zigbee2MQTT
/bridge/info; anything pegged at 90%+ for a minute is a warning. - Last OTA batch — device count, start time, and duration, so I never overlap batches.
A minimal template sensor for the worst-link watch:
template:
- sensor:
- name: "Zigbee worst RSSI"
unit_of_measurement: dBm
state: "{{ states | selectattr('entity_id', 'search', 'sensor\\.z2m_') | map(attribute='attributes.linkquality') | select('!=', none) | map('int') | min }}"
The dashboard does not fix a mesh, but it turns a "random drop at 3am" from a mystery into a graph that tells me exactly which link failed and when.
9. FAQ
Q: Why do my Zigbee devices drop out as I add more?
Because the path to the coordinator gets longer. End devices route through routers; as hops climb past 3 the link gets flaky and retransmissions pile up. The fix is placement, not patience: add mains-powered routers to shorten the path and keep every end device within 2 hops of a router.
Q: What is the difference between a coordinator, router, and end device?
The coordinator is the single network heart, holds the network key, and must not be changed casually. Routers are mains-powered relays that extend the mesh and forward traffic. End devices are battery leaves that sleep, wake on an interval, and talk to exactly one parent.
Q: What channel should I use for Zigbee?
Avoid the WiFi bands. Scan channels 11-26 and pick the quietest one that does not sit under an active WiFi channel. Coexistence with 2.4GHz WiFi is the single biggest cause of intermittent drops, and it is the cheapest problem to fix.
Q: What should I watch in Zigbee2MQTT graph view?
The hop count and RSSI per link. A link that jumped from 2 to 4 hops, or an RSSI that fell below your zone threshold, predicts a device about to drop. Fix the route before the device vanishes, because once it is offline you are re-pairing, not routing.