An MQTT ACL (access control list) is the set of rules a broker uses to decide which authenticated client may publish to or subscribe to which topics. This article explains the concepts, shows how they look in a real broker configuration using Mosquitto, describes what happens when a client is denied, and gives topic design patterns that keep authorization simple whether you use fine-grained ACLs or tenant-level isolation.
Authentication Versus Authorization
The two are easy to confuse, and brokers handle them in different stages:
- Authentication answers "who are you?" It happens once, at CONNECT, using a username and password, a client certificate or a token. See MQTT username password authentication for the details.
- Authorization answers "what may you do?" It is checked on every SUBSCRIBE and, depending on the broker, on every PUBLISH. ACLs are the most common way to express it.
An authenticated client with no authorization rules can typically read and write everything its broker or tenant allows. ACLs narrow that down per user, per client ID or per role.
What an MQTT ACL Rule Contains
Whatever syntax a broker uses, each rule combines the same elements:
| Element | Examples |
|---|---|
| Subject | A username, a client ID, a role, or "everyone" |
| Action | Read (subscribe and receive), write (publish), or both |
| Topic filter | factory/line1/#, devices/+/cmd, devices/%u/# |
| Decision | Allow or deny |
Rules are matched against the topic of each operation. For subscriptions, many brokers check the requested filter itself, so a client allowed to read devices/sensor-01/# cannot sneak a broader devices/# subscription past the check.
Mosquitto MQTT ACL Example
Mosquitto is a convenient reference because its file-based ACL is short and readable. Authentication and the ACL file are enabled in mosquitto.conf:
# mosquitto.conf
listener 8883
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
allow_anonymous false
password_file /etc/mosquitto/passwd
acl_file /etc/mosquitto/acl
The ACL file itself:
# /etc/mosquitto/acl
# Rules before any "user" line apply to anonymous clients (none here)
# A backend service that reads all telemetry and sends commands
user backend
topic read factory/+/+/telemetry/#
topic write factory/+/+/cmd/#
# A dashboard that may only read
user dashboard
topic read factory/#
# Pattern rules apply to every user.
# %u = username, %c = client ID
pattern write factory/+/%c/telemetry/#
pattern read factory/+/%c/cmd/#
pattern write factory/+/%c/status
With these rules, a device that connects as client ID cnc-07 can publish factory/line2/cnc-07/telemetry/spindle and receive factory/line2/cnc-07/cmd/stop, but it cannot read another machine's commands or publish as another machine. The pattern rules scale to thousands of devices without one entry per device, which is the main reason to put the client ID in the topic path. Mosquitto also supports topic deny lines and a dynamic security plugin with roles and groups; the Mosquitto documentation covers both.
Reload the broker after editing the file, then test:
# Allowed: cnc-07 publishes its own telemetry
mosquitto_pub -h localhost -p 8883 --cafile ca.crt -V mqttv5 \
-i cnc-07 -u cnc-07 -P "$PW" -t 'factory/line2/cnc-07/telemetry/spindle' -m 1200 -q 1
# Denied: cnc-07 publishes as cnc-08
mosquitto_pub -h localhost -p 8883 --cafile ca.crt -V mqttv5 \
-i cnc-07 -u cnc-07 -P "$PW" -t 'factory/line2/cnc-08/telemetry/spindle' -m 1200 -q 1
What Happens When a Client Is Denied
Denied operations do not always produce an obvious error, which is why ACL problems are notoriously hard to debug.
| Operation | MQTT 3.1.1 | MQTT 5.0 |
|---|---|---|
| SUBSCRIBE denied | SUBACK return code 0x80 (failure) | SUBACK reason code 0x87 Not authorized |
| PUBLISH QoS 0 denied | Dropped silently | Dropped silently |
| PUBLISH QoS 1/2 denied | Usually acknowledged and dropped | PUBACK/PUBREC may carry 0x87, broker dependent |
Practical debugging tips:
- Always inspect SUBACK codes. Many libraries expose them in a callback; log them at startup.
- Use MQTT 5 and QoS 1 when testing. Reason codes on acknowledgements make denials visible.
- Test from the device's identity. Testing with an admin account proves nothing about the device's rules.
- Check the broker log. Most brokers log denied publishes and subscriptions at a debug level.
Designing Topic Namespaces for Simple Authorization
Good ACLs start with good topics. If identity and direction are visible in the topic path, the rules collapse to a few patterns. If they are not, you end up with a long list of special cases.
Put Identity in the Path
Include the client ID or device ID at a fixed level: factory/<line>/<deviceId>/.... Pattern rules like %c only work when the identity is a full topic level.
Separate Directions
Use distinct branches for data flowing up and commands flowing down: .../telemetry, .../status, .../cmd. Devices get write on the first two and read on the third.
Separate Tenants or Customers at the Top
If one broker serves several customers, make the customer the first level: <tenant>/.... Each tenant's credentials are then limited to one subtree.
Keep Shared Data in a Clearly Named Branch
Configuration broadcast to all devices goes in broadcast/... or config/... with read-only access for devices.
The MQTT topic hierarchy examples article shows complete trees built on these principles.
ACL Design Checklist
- Deny by default; allow only what each role needs.
- Give devices write on their own telemetry and status, read on their own commands, nothing else.
- Give dashboards read-only access.
- Give backend services write on command branches, and audit who holds those credentials.
- Never grant
#to a device credential. - Keep the rule file or role definitions in version control and review changes.
- Test each role with its real credentials after every change.
Authorization on ConnectMQ
ConnectMQ does not provide per-topic ACLs. Isolation on ConnectMQ works at the tenant level: each customer's broker traffic is isolated from every other tenant, credentials are generated per broker, and client IDs are bound to the issued username and password, so a client cannot claim another client's identity. Per-plan quotas, flood protection and IP blocking after repeated failed logins limit what a misbehaving client can do.
What that means for design:
- Use separate brokers as your security boundary. If two groups of clients must never see each other's data, put them on different brokers rather than relying on topic rules. A Dedicated plan gives you an isolated cluster when that boundary must also be physical.
- Keep untrusted clients off the broker. Browser users and third parties should talk to your backend, which holds the broker credentials and decides what to publish on their behalf.
- Design topics as if ACLs might come later. Identity in the path and separate command branches cost nothing now and keep options open if you move part of the system to a broker with topic rules.
- Validate commands on the device. A device should check that a command is well formed and expected before acting, regardless of how the broker authorizes it.
This combination of tenant isolation and topic discipline covers many fleets well. If your project needs per-device topic restrictions inside one shared namespace, plan for a broker that supports topic-level ACLs for that part of the system. For the bigger picture, see MQTT security best practices and is MQTT secure.
Get Started with ConnectMQ
Run your devices on a hosted MQTT broker with tenant isolation, TLS, generated credentials bound to client IDs and IP blocking for failed logins. Create a free Serverless broker at app.connectmq.com, no credit card required, and prototype topic designs first on the public MQTT test broker.
Frequently Asked Questions
What is an MQTT ACL?
It is a list of rules that tells a broker which users or clients may publish or subscribe to which topic filters. It is checked after the client has authenticated.
Does ConnectMQ support per-topic ACLs?
No. ConnectMQ isolates tenants from each other and binds client IDs to issued username and password credentials, but it does not enforce per-topic read and write rules within a broker.
Why does my denied publish not return an error?
In MQTT 3.1.1 there is no way for the broker to report a denied publish, so it is dropped silently. MQTT 5 brokers can return reason code 0x87 on QoS 1 and 2 acknowledgements.
Should ACL rules use the username or the client ID?
Use whichever identifier is unique per device and cannot be chosen freely by the client. If the broker binds client IDs to credentials, either works; otherwise prefer the username.



