private@homelab: ~/latest
local-first guides · privacy-aware · no noisy tracking
private@homelab:~$ cat guides/article.md
· ·
8–12 minutes
read

Zigbee2MQTT MQTT Broker Security: A Practical Checklist

A practical checklist for securing Zigbee2MQTT's Mosquitto broker and frontend, from the add-on's anonymous-access default onward. Every fix stays local.

Everything in a Zigbee2MQTT setup routes through one component, the MQTT broker, usually Mosquitto, usually running as the Home Assistant add-on. Get that broker’s defaults wrong and “it’s all local” stops being a security property and becomes a false sense of one. This is a checklist for closing the gaps that actually matter, sourced from Zigbee2MQTT’s own security guide and the add-on’s documented config options.

It’s aimed at anyone already running Z2M on Mosquitto, standalone or via the HA add-on, who wants to go through the hardening steps once and be done. Every fix here is a local config change. Nothing requires a cloud account or a subscription. This assumes you’re already comfortable with basic Z2M operation, pairing devices, reading the network map, clearing a stuck ghost device, or setting up groups and scenes. This piece is specifically about what happens once that setup is running unattended.

Why “it’s all local” doesn’t mean secure by default

Local-first means your Zigbee traffic isn’t routed through a vendor’s servers. It says nothing about who else on your network, or worse, who else reachable from outside it, can talk to your broker. Anyone who can publish to the right MQTT topic can turn on your lights, unlock a Zigbee-controlled deadbolt, or trigger a relay. The broker is the single point every device trusts, which makes it the single point worth locking down first. It’s the same lesson that surfaces when you look at what Xiaomi’s Home Assistant integration actually sends or whether Tuya’s app phones home. Not phoning home and not being reachable by anyone else are two separate properties, and only one of them is guaranteed by default.

Check whether your Mosquitto add-on is running wide open

Note · before you start

This applies whether you run Mosquitto as the official Home Assistant add-on or standalone. The config file paths differ, but the settings below map directly either way.

The official Home Assistant Mosquitto add-on has historically documented its anonymous option as defaulting to true, and its plain option, the unencrypted port 1883 listener, as defaulting to true as well. There’s an important nuance here: with anonymous: true and no logins defined, the broker accepts fully unauthenticated connections; but once any logins entry exists, an anonymous client is restricted to read-only. That still isn’t nothing, but it’s a different risk than full control. The exact default has also shifted across add-on versions, and the community thread that debated this reflects that churn rather than settling it. So don’t take any single blog’s word for it, this one included. Open the add-on’s Configuration tab and read your own anonymous and plain values before assuming your broker is either locked down or wide open.

Locking down the broker: logins and disabling anonymous access

Securing the broker means adding at least one set of credentials under logins and flipping anonymous to false. Every client that talks to the broker after that, Zigbee2MQTT included, needs to authenticate with a username and password.

Mosquitto add-on configuration
logins:
  - username: z2m_broker
    password: !secret mqtt_password
anonymous: false
plain: false

With logins and anonymous: false set, update Zigbee2MQTT’s own configuration.yaml to pass the matching credentials in the mqtt server URL or its user/password fields, otherwise it loses its connection to the broker the moment you save.

TLS is the other half of “secure,” and it’s where Z2M’s own docs are explicit about a specific mistake. If you enable TLS between Z2M and the broker, don’t set reject_unauthorized: false to work around a certificate error.

Warning

Setting reject_unauthorized: false disables certificate validation entirely. It gets a broken TLS setup working, but it also defeats the point of using TLS in the first place, since it will happily accept a connection from anything presenting any certificate. Fix the certificate, don’t disable the check.

Locking down the Zigbee2MQTT frontend

The frontend, the web UI you use to pair devices and check the network map, has no built-in authentication by default. Zigbee2MQTT’s own guide is blunt about the implication. Whoever can load that page has the same level of control over your Zigbee network that you do, including rejoining devices, removing devices, and issuing any command the UI exposes.

If the frontend doesn’t need to be reachable from outside the host it runs on, the fix is to set an auth_token and bind it to 127.0.0.1 instead of 0.0.0.0. If you do need remote access, put it behind a reverse proxy with HTTPS and WSS rather than exposing the raw frontend port.

configuration.yaml
frontend:
  enabled: true
  host: 127.0.0.1
  port: 8080
  auth_token: !secret z2m_frontend_token

Honestly, I think an unauthenticated frontend is a stranger default for Z2M to ship than the Mosquitto add-on’s anonymous setting. The broker at least has a plausible “local network only” excuse. A web UI with full device control and zero login screen is the kind of thing that’s fine until you forward a port for something unrelated and forget it’s sitting there.

Locking down the Zigbee network itself

The MQTT layer isn’t the only thing worth rotating. Z2M recommends generating a fresh, random Zigbee network encryption key rather than keeping whatever key shipped with your coordinator or got set during a first-run wizard.

configuration.yaml
advanced:
  network_key: GENERATE

Setting network_key: GENERATE has Z2M generate a random key on next start instead of reusing a default or previously-set one. Regenerating the key requires re-pairing every device on the network, since each device holds the old key. This isn’t something to do casually on a mesh with dozens of devices already paired.

Two more recommendations round out the network-key guidance. Use install codes for initial pairing where a device supports them, since they provide a more secure key-transport path than the default in-band exchange. Keep Touchlink in mind on an always-on install, too. Touchlink is a proximity-based commissioning feature: with the right initiator hardware nearby, physical proximity alone can be enough to trigger a join or a factory reset on some devices. Note that Z2M doesn’t expose a single configuration.yaml switch that turns off other people’s ability to Touchlink your devices, that behavior lives in the device firmware, not in Z2M. In practice this is a physical-access consideration more than a network one, so the realistic mitigation is controlling who has physical proximity to your coordinator and devices rather than a config flag.

File permissions and running as a non-root user

Z2M’s configuration.yaml and any secret.yaml it references hold the MQTT broker credentials and the Zigbee network key in plain text. Z2M’s own security guide recommends two things here. Run Zigbee2MQTT as a dedicated, unprivileged user rather than root, and lock the config files down to 600 permissions so only that user can read them.

Terminal
chmod 600 configuration.yaml secret.yaml

If you’re running the HA add-on, this is handled for you inside the add-on’s container. It matters most for standalone installs where Z2M runs directly on a shared host.

The accidental-exposure failure mode: port forwarding and UPnP

The scariest real-world failure mode here isn’t a subtle broker misconfiguration. It’s a router accidentally forwarding MQTT’s port to the public internet. This isn’t Z2M-specific. Any Mosquitto instance can end up exposed this way, usually through a manual port-forward rule set up for something else and forgotten, or through UPnP letting a device on the network open a port on its own without asking.

Note · compatibility

Check your router’s port-forwarding table for any rule pointing at 1883 or 8883 on an internal address like 192.168.10.20 (representative, not a real address). If UPnP is enabled on your router, consider disabling it on the segment your IoT devices and broker live on.

Worth knowing: an authenticated, non-anonymous broker with TLS and a strong password is far less of a risk even if it does end up reachable from outside. But that’s a second layer of defense, not a substitute for checking your router’s forwarding rules directly. If you haven’t segmented IoT traffic onto its own VLAN yet, that’s the broader companion move to everything in this checklist: putting your IoT devices and broker on a dedicated VLAN.

Quick checklist recap

Component Default risk Fix
Mosquitto add-on Anonymous access + unencrypted port enabled Set logins, anonymous: false, plain: false
Z2M ↔ broker TLS reject_unauthorized: false silently disables cert checks Fix the certificate instead of bypassing validation
Z2M frontend No authentication, full device control exposed Set auth_token, bind to 127.0.0.1, or reverse-proxy with HTTPS/WSS
Zigbee network key Default or previously-set key reused indefinitely network_key: GENERATE (requires re-pairing all devices)
Config files Contain MQTT credentials + network key in plain text chmod 600, run Z2M as a non-root user
Router Accidental port-forward or UPnP exposes broker publicly Audit forwarding rules, disable UPnP on the IoT segment

FAQ

Is Zigbee2MQTT’s MQTT connection encrypted by default? No. TLS between Z2M and the broker is opt-in configuration, not a default.

Does Home Assistant’s Mosquitto add-on allow anonymous connections out of the box? It historically documents anonymous and plain as defaulting to true, though the exact behavior has varied across add-on versions and an anonymous client is read-only once any logins entry exists. Check your own add-on’s Configuration tab rather than assuming — and set logins with anonymous: false regardless.

Can someone control my smart home if they get access to my MQTT broker? Yes. Any client that can publish to the right topic can command any device Z2M has exposed, which is the reason the whole checklist above exists.

Do I need a username and password if MQTT never leaves my LAN? It’s a reasonable question, but “never leaves my LAN” depends on your router never misrouting a port, which is exactly the accidental-exposure failure mode covered above. Credentials cost you almost nothing to set up.

What happens if my Zigbee network key leaks? Anyone with the key and the right hardware could decrypt Zigbee traffic on your network or potentially join the mesh. Regenerating the key with network_key: GENERATE and re-pairing devices is the direct fix, though it’s disruptive enough that it’s worth doing before you have dozens of devices paired, not after.

What this checklist verified, and what to check yourself

Every setting above is sourced from Zigbee2MQTT’s own official security guide and the Home Assistant Community’s Mosquitto add-on thread, not from hands-on testing on a bench. This is a documentation-synthesis piece. The config keys, the stated defaults, and the TLS warning all match what those sources say, not something independently observed running on test hardware.

Every fix here, broker credentials, frontend auth, network key rotation, file permissions, is a config change on hardware you already own. None of it phones home, and none of it requires a cloud account.

The one piece that isn’t fully in your control is the Mosquitto add-on’s shipped defaults. Home Assistant, not Zigbee2MQTT, decides whether anonymous and plain default to true, and that default has genuinely shifted across add-on versions in the past, so the value you’ll see on your install depends on which version you’re running. That’s exactly why the checklist tells you to open the Configuration tab and read your own values rather than trusting any stated default, this article’s included.

After making these changes, check that Z2M still connects to the broker with the new credentials before closing the terminal. Confirm the frontend actually prompts for the auth token in a private or fresh browser session. Then go back to your router’s admin page one more time to confirm no forwarding rule survived the changes above.

local-firstHome Assistantno-cloud