Skip to Content

Getting in is not the same as being allowed

past the front door
EMQX Guide · Part 4

Getting in is not the same as being allowed

Part 3 finished with a client that connects: it has a username, it has a password, and EMQX says yes at the door. That yes answers exactly one question — may you connect. It says nothing about which topics the client may read, which it may write to, or whether anything it sends can be read by whoever else is on the network. This part closes both gaps. Authorization rules pin each account down to the topics it genuinely needs. Listeners decide which port and which protocol a client arrives on, including the encrypted ones.

First match
One rule settles a publish or a subscribe. Everything below it in the chain never gets a turn
5 ports
1883, 8083, 8084, 8883 and 18083 — what a Home Assistant install actually meets
120 seconds
How often EMQX reloads certificates by default, so swapping one need not mean a restart
Two questions, not one

Authentication asks who you are. It never asks what you want

The authentication you set up in Part 3 controls who may connect. It does not control what a client may do to which topics once it is in. Those are two different questions, and EMQX answers them with two different mechanisms. Here is the shape of the problem in a normal house.

You wantedThe studio sensor account reads the studio sensors and controls its own switches. Nothing else.
You gotA valid username and password, and an account that can subscribe to every topic on the broker — including the ones belonging to the lock on the back door.
You also gotA cheap sensor that publishes to a topic it was never meant to touch, because nothing was ever written down saying it could not.

Authorization is what fixes that. You keep access control list rules in EMQX's built-in database, you match publish and subscribe permissions on who plus topic plus action, and you learn the order in which allow and deny get applied. By the end of this part you can tighten Home Assistant and each device down to the topics they are supposed to touch, which is what “least privilege” means in practice.

In plain terms

It is a hotel key card. Reception checking your booking and handing you a card is authentication — they established that you are the person on the reservation. The card then opening room 402 and the gym, but not room 403 and not the manager's office, is authorization. Two different checks, done by two different parts of the building. A hotel that only did the first one would hand every guest a master key.

The second half of this part is about the other side of the same connection: which door the client came in by. EMQX 5.8.9 lets one broker listen on several entry points at once — standard MQTT over TCP, WebSocket, secure WebSocket, and connections encrypted with SSL/TLS. If you want some devices on an encrypted port, Home Assistant on WebSocket and your own testing on 1883, you need to know what those listeners are and how certificates work.

The authorizer chain

The first rule that matches decides, and the rest never run

Authorization controls permissions on MQTT's publish and subscribe operations. You reach it in the Dashboard sidebar at Access Control → Authorization. EMQX offers four kinds of backend to hold the rules:

  • ACL File — the rules live in a file.
  • Built-in Database — EMQX's own storage, edited in the Dashboard. This is what the rest of this part uses.
  • An external database — MySQL, PostgreSQL, MongoDB or Redis.
  • HTTP Server — EMQX asks a web service of yours for the verdict.

Order is the part that surprises people. Every time a client wants to publish or subscribe, EMQX walks a chain of authorizers in order. Each authorizer holds its own set of access control list rules. EMQX starts matching at the first authorizer, and the first rule that matches that client decides — its allow or deny is applied, and nothing further is consulted. If nothing inside an authorizer matches, EMQX moves on to the next one. When nothing matches anywhere, the global default in Global Settings decides.

flowchart TD
  A["A client sends PUBLISH or SUBSCRIBE"] --> B["Authorizer 1: walk its ACL rules in order"]
  B -->|"a rule matches"| M["That rule decides"]
  B -->|"nothing matches"| C["Authorizer 2: walk its ACL rules in order"]
  C -->|"a rule matches"| M
  C -->|"nothing matches"| G["Global Settings decides the no-match case"]
  M --> AL["Allow: the publish or subscribe goes through"]
  M --> DE["Deny: ignore the request, or disconnect the client"]
  G --> AL
  G --> DE
Where a verdict comes fromTwo authorizers are drawn; a real chain can be any length, and each extra one adds another “nothing matches” hop before Global Settings is reached. Allow on the left and Deny on the right each take two arrows — one from a matched rule, one from the global default — because both routes end in the same two verdicts.

Authentication and authorization are complementary layers, and the built-in database ACL is where they meet: a rule can key off a username or a client ID, so the identity that authentication established is the identity that authorization judges.

TermPlain EnglishIn one line
Authorizationwhat you may doDecides publish and subscribe permissions
ACLaccess control listThe set of topic permission rules
Authorizerauthorization checkerOne unit that runs a rule set against a backend
Allowlet it throughLets this action and topic pair through
Denyblock itBlocks this action and topic pair
Actionthe operation being checkedpublish or subscribe
Topic Filtertopic patternA rule's topic, which may contain wildcards

Global Settings holds two decisions, and both matter. The first is the fallback: when no authorizer finds a matching rule, EMQX can always allow or always deny. The second is what a denial actually does: ignore, which drops the request silently, or disconnect, which cuts the client's connection.

In a home setup, most people set the no-match case to deny. That is the safe side of the switch: if it is not on the allow list, it is blocked. The cost is that you have to write an allow rule for every client that has a job to do — which is the work this part is asking you to do anyway.
Writing ACL rules

Who, which topic, which action, allow or deny

A built-in database ACL rule describes a permission along three dimensions — client, topic, action — one rule per row saying who may do which action on which topic. You can pick all users to set a baseline, then stack rules for a specific client ID or username on top of it.

The topic in a rule may carry the wildcards you met in Part 2, and their rules do not bend here: + stands for exactly one level, and # stands for the rest of the topic and may only appear at the end. Collect your switch topics under a single filter and one rule covers the lot:

living-room/switch/+/state — one rule covering the state topic of every living-room switch, whatever each switch is called.
device/+/state — the state topic of any single device, one level deep.
device/# — everything under device/, however many levels deep it goes. Very wide. Be careful where you put it.

Allow lets that action-and-topic pair through; Deny blocks it. The same topic can carry both an allow rule and a deny rule, and which one wins is settled together by the authorizer order and the global settings. That is where the classic trap lives.

Say you put a very broad deny on device/# in front, and an allow on device/+/state behind it. The over-broad deny matches first. The allow behind it never gets a turn.

flowchart TD
  A["Client subscribes to device/kitchen-lamp/state"] --> R1["Rule 1 in the list: Deny, subscribe, device/#"]
  R1 -->|"this filter matches first"| D["Denied. The chain stops here"]
  R1 -.->|"never consulted"| R2["Rule 2 in the list: Allow, subscribe, device/+/state"]
The rule you wrote, and the rule that ranThe dotted edge on the right is the only path to Rule 2, and EMQX never takes it. Both rules are correct on their own; the list order alone is what makes the second one dead.
In plain terms

It is the list of instructions taped to the office fridge. Somebody reads down from the top and does the first line that applies to them. A line at the top saying “nobody touches the coffee machine” means the line three down saying “Anna refills the beans on Fridays” is never read by anyone. The beans line is not wrong. It is just underneath.

So think the narrow, precise rule through before you write it, and then order the rules by weight. On the Authorization List you can drag rows with the mouse, or reorder them from the Actions column; EMQX starts at the first one and works down until an ACL rule matches.

Least privilege, and why it is worth the extra typing. The usual advice for a home setup is to allow only the publish and subscribe topics each account genuinely needs, rather than opening everything first and then patching holes with deny rules. The reason is containment: the wider the permissions, the larger the area that can be tampered with if one account is compromised or stolen. Build it the allow-list way and even a gap in your ordering or your global settings leaves the exposure small.
Build one, rule by rule

From an empty Authorization list to a rule you have tested

The built-in database needs no external service and no extra parameters, which makes it the right first authorizer for a home broker. Six steps, and the last one is the one that tells you whether the first five worked.

  1. Step 1

    Open the Authorization page

    Log in to the Dashboard and go to Access Control → Authorization in the sidebar. Click Create at the top right.

  2. Step 2

    Create a built-in database authorizer

    Choose Built-in Database as the backend. It needs no extra parameters, so there is nothing to fill in. Click Create to finish, and you land back on the Authorization List with your authorizer on it.

  3. Step 3

    Add your first ACL rule

    On that authorizer, go to Actions → Permissions to edit the rules. Each rule takes four things: the client identity (client ID, username, or all users), the topic — which may contain + and # — the action, publish or subscribe, and Allow or Deny. Save.

  4. Step 4

    Put the rules in the order you meant

    Narrow rules go above broad ones. On the Authorization List, drag the rows with the mouse or use the reorder controls in the Actions column. If you have written a # rule anywhere, this is the step where you check nothing useful is sitting underneath it.

  5. Step 5

    Set the global defaults

    Back on the Authorization list, click Settings. Decide whether “no rule matched” means allow or deny, and whether a denial should ignore the request or disconnect the client. For a home broker, deny plus ignore is the usual pair — blocked, without a storm of reconnects.

  6. Step 6

    Prove it with a real publish and a real subscribe

    Take your test account, publish to a topic that should be allowed and to one that should be denied, then subscribe to each in turn. Confirm that allow and deny behave the way the rules say. A rule list that has never been tested is a guess written down neatly.

Change one thing at a time. When a rule does not behave, the two candidate causes are almost always the rule itself and its position in the list. Edit one, retest, then edit the other. Changing both at once and retesting once tells you nothing about which change did it.
The five ports

One broker, several doors, and one of them is not MQTT at all

A listener is the server-side point at which EMQX accepts MQTT client connections. Each one has a transport protocol — TCP, SSL, WebSocket or WSS — plus a bind address and a port, and you can set how many concurrent connections it accepts with max_connections. One EMQX instance runs several listeners at the same time, so clients with different jobs can use different protocols and ports.

The official docs list four main transports: TCP on 1883, SSL on 8883, WebSocket on 8083 and Secure WebSocket on 8084. The Dashboard itself is served by the add-on on 18083 over HTTP, which is why the number people quote day to day is five rather than four. Because the add-on runs in host_network mode on the Home Assistant host, all five are the host's own ports.

PortProtocolWhat it is for
1883MQTT (TCP)Standard MQTT; what Home Assistant and Zigbee use most
8083MQTT/WSMQTT over WebSocket. The browser test client uses this
8084MQTT/WSSSecure WebSocket — a WebSocket encrypted with TLS
8883MQTTSMQTT over SSL/TLS: standard MQTT, encrypted
18083HTTPThe EMQX Dashboard web interface

Per the docs, the MQTT path on a WebSocket listener defaults to /mqtt. Note that it is only 18083 that is not an MQTT listener at all — it is the web interface you have been clicking around in, and no client connects to it with an MQTT library.

flowchart LR
  A["Zigbee bridge and sensors"] --> P1["1883 MQTT over TCP"]
  B["A device holding a certificate"] --> P2["8883 MQTT over SSL/TLS"]
  C["Browser test client"] --> P3["8083 MQTT over WebSocket"]
  D["Browser, encrypted"] --> P4["8084 MQTT over WSS"]
  E["You, reading the Dashboard"] --> P5["18083 Dashboard over HTTP"]
  P1 --> X["One EMQX 5.8.9 instance"]
  P2 --> X
  P3 --> X
  P4 --> X
  P5 --> X
Five doors, one buildingAll five arrows converge on the same instance on the right. Four of them carry MQTT; the fifth, 18083, is the Dashboard web interface — same software, a different kind of client. Every arrow points inward, which is the point: each of these is a way in, and each one you leave open is a way in for somebody else too.
In plain terms

A shop has a front door for customers, a roller shutter at the back for deliveries, and a side door the staff use. All three go into the same shop. Which one you walk through changes nothing about the stock on the shelves — but a back door nobody has used in a year and nobody has checked is still a way in. Shops lock those. So should you.

You do not have to keep all five in play. You may have some devices on 1883 only and others on the encrypted 8883; anything running in a web browser goes to 8083, or 8084 for the secure version. Pick the one you actually need as each requirement comes up, and leave the rest closed. To see what you currently have, open Management → Listeners in the sidebar — the existing listeners are listed with their protocol and port, and Add creates a new one.

TLS, certificates and mTLS

Encrypting the traffic, then checking who is at the other end

TLS, Transport Layer Security, encrypts data at the transport layer; SSL was its predecessor, which is why you see both names on the same screens. EMQX uses TLS in three places: for MQTT connections, for Data Integration reaching out to external resources, and between cluster nodes. Each of those can be one-way or mutual.

In one-way TLS the client checks the server's identity certificate. In mutual TLS, or mTLS, the server checks the client's certificate as well, which blocks a man-in-the-middle attack. SSL/TLS needs three files:

certfile — the server certificate. In the Dashboard this is the TLS Cert field.
keyfile — its private key. In the Dashboard, TLS Key. This is the file that must stay secret.
cacertfile — the list of trusted certificate authorities. In the Dashboard, CA Cert.

EMQX ships with a set of certificates meant only for testing, under etc/certs. That is why the 8883 listener works out of the box as a one-way test setup with no work from you. It is also why it is not ready for anything real.

In plain terms

A test certificate is a name badge you printed at home on the way to the conference. It has your name on it and it looks like a badge. Nobody checked it against anything, and nobody can. The badge handed to you at the registration desk says the same words, but there is a list behind the desk that it came off. A certificate from a trusted authority is the second kind, and that list is what the client is really checking.

sequenceDiagram
  participant C as Client
  participant E as EMQX SSL listener
  C->>E: open a connection on 8883
  E->>C: send the server certificate
  C->>C: check it against a trusted CA
  Note over C,E: one-way TLS is finished here
  E->>C: ask for the client certificate
  C->>E: send the client certificate
  E->>E: check it, and refuse if there is none
  E->>C: handshake done, MQTT starts
The handshake, with and without mTLSRead down to the note and stop, and that is one-way TLS. The three lines between the note and the final handshake line happen only when TLS Verify is enabled, and the refusal on the third of them is what Fail If No Peer Cert set to true buys you.

Here is how to work on an SSL listener in the Dashboard.

  1. Step 1

    Open the SSL listener's edit page

    Go to Management → Listeners and click the Name of the SSL listener — on a fresh install it is called default. The edit page opens.

  2. Step 2

    Swap in your own certificates

    Replace TLS Cert with your server certificate, TLS Key with its private key, and CA Cert with the trusted CA. Click Update when you are done. Before you go live, these must be certificates issued by a CA your clients already trust, for a name your clients actually connect to — not the built-in test set.

  3. Step 3

    Turn the mTLS handshake on, if you want it

    On the same page, the TLS Verify switch decides whether the client certificate is checked. To require mTLS, set TLS Verify to Enable and Fail If No Peer Cert to true. A client with no certificate then fails during the handshake — every client, including the ones you forgot about.

  4. Step 4

    Get back to a known state if you need to

    The Listeners page has a Reset control that returns the listener to the built-in test certificates. When a certificate change breaks 8883 and you cannot tell which of the three files is at fault, resetting is the fastest way to prove the listener itself is fine.

The private key is the whole secret. Keep the key file readable only by the system and by you. A server certificate is public by design — it is handed to every client that connects. The key is not, and anyone holding a copy of it can impersonate your broker to every device in the house.
Replacing a certificate does not always mean restarting EMQX. EMQX reloads certificates every 120 seconds by default, so a swapped server certificate is normally picked up on its own within that window.

One more sequencing point, because it catches people out: prepare a certificate for every device before you enable mTLS, or every device without one is locked out the moment you click Update. One-way TLS is already enough to keep the traffic from being read. Move up to mTLS when you specifically want “only devices holding a certificate I issued may get in” — and when you have actually issued them.

Conflicts, and what a restart undoes

Two neighbors on the same port, and one setting that does not stick

Because of host_network, those five ports share their numbers with every other service on the Home Assistant host. Two collisions come up far more than the rest.

What clashesPortWhat the sources say
Mosquitto1883The add-on README states plainly that it cannot run at the same time as Mosquitto — both want 1883
WebRTC (AlexxIT)8083Shares 8083 with EMQX's WebSocket listener

The add-on README gives the procedure. Stop the conflicting service — Mosquitto, say — first, then start EMQX. If you want both running, go to Management → Listeners, change the port on the listener that clashes or add a listener on a different port, then start that other service again. You can also keep only the listeners you need, 1883 and 8883 for example, and close the rest, which shrinks the surface a scanner can find.

A Dashboard port change can be undone by the next restart. The official docs are explicit: if a listener is defined in emqx.conf, a change made in the Dashboard only holds until the next EMQX restart. To make a setting stick from startup onwards, set it in a config file or with an environment variable — the add-on's EMQX_LISTENERS__* variables. Those are covered with the rest of the configuration story in Part 7 of this series.

This is worth internalizing now rather than discovering during an update. The symptom is unmistakable and deeply confusing the first time: you change a port, everything works, EMQX restarts a week later for an unrelated reason, and the old port is back with no trace of what you did.

When it does not behave

The symptom you meet, and the thing to check first

Two tables. The first covers permissions, the second covers ports and certificates. Both are ordered roughly by how often they come up.

SymptomLikely causeWhat to do
The client connects but cannot publish to a topic An authorization rule is blocking that action or that topic Go to Authorization → Permissions and check whether that account or client has an action-plus-topic-plus-Allow rule covering the topic you want. Then check whether the no-match value in Global Settings is deny
There is an Allow rule and it is still blocked Rule order. EMQX goes by the first match If a broader # deny sits in front and matches first, the Allow behind it never gets a turn. Simplify the rules or reorder them
You want to block a client and nothing happens No deny rule reaches it before something else allows it Change the rule aimed at it to deny, or add a topic-plus-subscribe-or-publish-plus-deny rule earlier in the order. Then confirm in Global Settings that a no-match is not an implicit allow
An external database authorizer shows Disconnected Wrong address, wrong credentials, wrong query syntax, or that database is not ready The configuration still saves, but it fails at run time. Fix the external side, go back to Overview to read its health, and confirm that what you picked really is the built-in database if that is what you meant
SymptomLikely causeWhat to do
EMQX fails to start and the log says the port is already in use Mosquitto on 1883, or WebRTC on 8083 Work out which port it is, stop the service that clashes, then start EMQX. If you want them to coexist long term, go to Listeners afterwards and change the port on one side
8883 stops accepting connections after a certificate change Mismatched files, wrong format, or a name that does not match Check that certfile and keyfile are a matching pair, that the CA and the server certificate are in PEM format, and that the CN or SAN on the server certificate matches the address the client connects to — otherwise you get a Hostname/IP does not match certificate error. Use Reset in Listeners to go back to the test certificates and isolate the problem
Every client without a certificate drops after you enable mTLS Nothing is broken. That is what mTLS is for Mutual authentication insists that the client present a trusted certificate. To relax it for now, set TLS Verify back to one-way; otherwise issue that client a trusted client certificate first
You changed a port in the Dashboard and the restart reverted it The listener is defined in emqx.conf A Dashboard change only holds until the restart. To make it permanent, override it at startup with the add-on's EMQX_LISTENERS__* environment variables
Questions people ask

The things that come up once the rules are in place

How do authorization and authentication divide the work?
Authentication confirms who you are first; authorization then decides whether you can do it. Authentication cannot stop a client from subscribing to someone else's topics — only authorization rules can allow or deny at topic granularity. Least privilege only starts once both are configured.
Which is better, the built-in database ACL or an ACL file?
For a home setup where you configure things in the Dashboard, want every change to stick and want it to feel obvious, pick the built-in database. An ACL file keeps the rules as text, which suits a more engineering-minded owner who wants them under version control, but it is less obvious to edit. For most add-on users the built-in database is enough.
Does a client get disconnected after a deny?
That depends on your Global Settings. EMQX allows either “drop the request after a denial” (ignore the message) or “disconnect the client outright” (disconnect). Pick ignore if you want the message blocked quietly, disconnect if you want the connection cut. Neither is absolute, and home setups often pick ignore to avoid a run of reconnects.
Why does least privilege keep coming up?
The wider the permissions, the larger the area that can be tampered with once an account is compromised or stolen. Open only the publish and subscribe topics each account needs right now and deny the rest — that keeps the damage from one broken device as small as it can be, and containment like this is what matters in home IoT.
Do I have to open all five ports, or can I close some?
You do not need all of them. Keep only the ports you actually use — 1883 plus 18083, for example — and stop or renumber the WebSocket and SSL listeners. The fewer you run, the safer you are: every extra open port is extra attack surface.
8883 and 8084 sound alike. What is the difference?
8883 is standard MQTT, encrypted — MQTT over SSL/TLS. 8084 is an encrypted WebSocket channel, MQTT over WSS. Both encrypt the traffic; they differ in the transport underneath. The first is for native MQTT clients over TCP, the second for the WebSocket a browser uses. Their unencrypted counterparts are 1883 and 8083.
One-way or mTLS — which should I choose?
Start with one-way: the client sends and receives encrypted and checks the server certificate, which is already enough to keep the traffic from being read. Move up to mTLS when you want only devices holding a certificate you issued to get in — but prepare a certificate for every device first, or every device without one will be locked out.
Can I use a certificate I signed myself?
For testing, yes. For production, no. What EMQX ships with is a test certificate, and the official docs say plainly that production needs a certificate issued by a trusted CA. A self-signed certificate also means installing your CA on every client before it will be trusted, which goes wrong easily in a home with many devices.
Next

Where to go from here

the core is done

Connect, prove who you are, and be allowed only what you need.

That closes the core of the guide: a client that connects, an identity the broker trusts, permissions pinned to the topics that account actually uses, and an encrypted door for the traffic that warrants one. Part 5 turns from letting messages through to doing something with them — the Rule Engine, where a SQL statement picks messages out of the stream and hands them to an action.

Open the full guide

Part 4 of the WoowTech EMQX Complete Guide series on the Apporo blog.

Adapted from the WoowTech EMQX Complete Guide, produced by WoowTech and released under CC BY 4.0. This adaptation is published by Apporo under the same licence.

Light · Air · Water · Control · apporo

in EMQX
Send your first message, then decide who else may