Secure deployment
Secure EMQX deployment: the risks of host_network, external exposure, rate limits, TLS requirements, protecting the admin surface, and the principle of least privilege.
Why this matters
Your EMQX carries the MQTT messages for the whole house, which also makes it one of the doors an attacker most wants to try: anyone who can connect, get past authentication or subscribe could read your sensor data or drive your devices. Woow EMQX runs in host_network mode, which opens its ports straight on the host, so the security boundary is thinner than a container confined to a virtual network — you have to hold that line by hand.
By the end of this chapter you will think of EMQX's defenses as layers: authentication decides who may connect, the ACL decides who may publish and subscribe, rate limits absorb a flood, TLS stops eavesdropping, and nobody else should be able to reach the admin surface (the Dashboard) directly. It all comes back to one principle — least privilege.
Core concepts
EMQX security covers four stretches of the path: Client → Listener (is there TLS, is there authentication), Authentication → Authorization (who may connect, and what they may do), resource protection (Limit, Blacklist, Flapping Detect), and the admin surface (access to the Dashboard and the API).
Because the Woow add-on uses host_network, all five ports (1883, 8083, 8084, 8883, 18083) are open straight on the host. If the Home Assistant host stays on a trusted local network the risk is manageable; but as soon as you forward a port on the router, put the host in a DMZ, or serve anything to the outside, those listener ports can be scanned from the public internet. Secure deployment comes down to one thing: expose only the listeners you need.
The official security checklist points out a fact that is easy to miss: until you configure Authentication, EMQX allows every client to connect. So setting up authentication first is not extra credit, it is something you finish before you start EMQX up.
Terms at a glance
| Term | Plain English | In one line |
|---|---|---|
| Host Network | host network mode | The add-on uses the host's ports directly |
| Exposure | attack surface | The range of listener ports that can be reached from outside |
| Rate Limit | rate limit | Caps the connection and publish rate so resources are not exhausted |
| TLS | transport encryption | Encrypts the traffic between a client and the broker |
| Least Privilege | least privilege | Give an account only the rights it needs to do its job |
| API Key | API key | The credential the REST API authenticates with, not the Dashboard password |
The first security step is to replace the defaults: the Dashboard's admin/public is public knowledge, so change it the moment you enable the add-on. Write API keys and MQTT client credentials as placeholders too (<your-password>, YOUR_TOKEN) and never print details of your local network.
Hands-on
Change the default admin password
If the Dashboard asks you to at first login, replace
publicwith a strong password of your own. If it does not ask, change it by hand in the Dashboard's user settings anyway, so nobody can log in with the published default.Set up Authentication
Go to Access Control → Authentication and create an authenticator (we recommend Password-Based → Built-in Database), then create real users in place of the default anonymous connection. Before you expose a listener you need at least this much: no anonymous connections.
Narrow the ACL with Authorization
Go to Access Control → Authorization and write each user's Allow rules to least privilege: which topics Home Assistant needs to publish and subscribe to, which ones Z2M needs. List the specific topics one by one, and do not open everything with
#.Add rate limits and review the listeners
On the Management → Listeners page, set a Rate Limit on every listener that faces outward (max connections per second, for example). Also confirm that 18083 (the Dashboard) is not directly exposed — keep it on the local network, or put authentication in front of it.
None of that covers TLS configuration: if your traffic crosses an untrusted network segment, you also have to configure certificates for 8883 (MQTTS) and 8084 (WSS). Chapter 8, Listeners/TLS, has the details.
host_network and external exposure
host_network in the Woow add-on means it uses the Home Assistant host's ports directly: 1883 (MQTT), 8083 (MQTT over WebSocket), 8084 (WSS, MQTT over secure WebSocket), 8883 (MQTTS), 18083 (the Dashboard). There is no container-level private network to isolate them.
The risk comes down to who can reach those ports. If the host stays on a trusted local network the threat is low; but if you open an inbound connection on the router, put the host in a DMZ, or expose 1883 through ngrok, you have pushed the broker onto the public internet. In that case:
- Open only the listeners you need; do not expose all five ports at once.
- Keep plaintext 1883 on the local network or use it only as a stopgap; for anything facing outward, prefer the encrypted 8883/8084.
- Restrict the Dashboard (18083) to a trusted network, use HTTPS, and allow only the accounts you need.
The security checklist in the EMQX docs adds one more: if a Proxy Protocol or WebSocket listener has no trusted proxy rewriting the source IP, turn off the forwarded-address headers, so IP-based authorization cannot be spoofed.
Rate limits and resource protection
Rate Limit (the limiter) is an EMQX 5.0+ mechanism that caps the rate at the entry point, so a single client or listener cannot be flooded. The Dashboard's Management → Listeners page sets a value per listener; you can also set it in emqx.conf.
| Type | UI label | What it does | Default behavior when exceeded |
|---|---|---|---|
| bytes_rate | Data Publish Rate | Bytes published per client per second | Stops taking messages from that client |
| messages_rate | Messages Publish Rate | Messages published per client per second | Stops taking messages from that client |
| max_conn_rate | Maximum Connection Rate | Connections that listener accepts per second | Stops taking new connections |
For example, emqx.conf can limit the default TCP listener like this (time units s/m/h/d, sizes KB/MB/GB):
listeners.tcp.default {
bind = "0.0.0.0:1883"
max_conn_rate = "1000/s"
messages_rate = "1000/s"
bytes_rate = "1MB/s"
}
When a listener is exposed, Rate Limit together with Blacklist and Flapping Detect will stop one client from flooding the broker or abusing reconnects. Set the Flapping threshold high enough that a normal reconnect does not trip it, so healthy devices are not locked out by mistake.
TLS requirements and protecting the admin surface
When traffic crosses an untrusted network, use TLS: EMQX's 8883 (MQTTS) and 8084 (WSS) are the encrypted listeners. Certificates should be issued by a trusted CA or by your own internal PKI, and rotated before they expire; to identify a device by its certificate, add X.509 authentication and mTLS (verify_peer).
The admin surface has a few hard rules of its own:
- Leaving the Dashboard on its default password is a warning sign — change it at first login, and make sure only the people who need it can reach it.
- The REST API should use an API Key (system → API Key) and be granted the smallest role possible; EMQX Open Source does not use RBAC, so every Dashboard user is an administrator.
- Bind the Dashboard to a trusted interface where you can: localhost, the local network, or a dedicated management segment.
Least privilege applies at every layer: give each device its own credentials, let the ACL allow only the topics that are needed, and do not open everything with a wildcard. The finer the controls, the smaller the hole a single leak opens.
Troubleshooting
- 1883 is scanned from outside and people start trying to connect: turn the listener off or restrict it to the local network first, and only consider exposing it once both authentication and the ACL are in place.
- Normal clients are dropped after you set a rate limit: check the time unit and the number in the limit, and raise the Flapping threshold to a level a normal reconnect will not trip, so healthy devices are not locked out by mistake.
- The TLS connection fails: check the certificate chain, that the private key matches, and the CA; with a self-signed certificate you also have to add that CA to the client's trust store.
- The Dashboard is reachable straight from the public internet: 18083 is the HTTP admin port and is the last thing you want exposed. Restrict it to the local network or a trusted connection, and switch to HTTPS with a strong password.
FAQ
Do I have to set up TLS for MQTT?
It depends on whether your traffic crosses an untrusted network. If Home Assistant and the devices only talk to each other on the same local network, that network is trustworthy enough — but still use strong passwords and an ACL. As soon as you need to reach outside or cross networks, encrypt with 8883/8084.
Will a rate limit slow down normal use?
Set it only on the listeners that face outward, keep the limit above your normal traffic, and you will barely notice it. What it blocks is one client flooding the broker; a normal household sends far less than the default limits, so it never bites. If something really is being throttled, try raising the limit.
After I change the Dashboard password, does MQTT use the same one?
No. The Dashboard password protects the admin interface; an MQTT client uses the account credentials from Access Control → Authentication. They are two separate sets, and both need a strong password.
Does EMQX Open Source have RBAC?
No. In EMQX 5.8 Open Source every Dashboard user is an administrator; RBAC (Administrator/Viewer) is an Enterprise feature. So on Open Source, keep the number of admin accounts as low as you can, and give the REST API an API key with the lowest role that works.