The Dashboard in front of you, and the five words underneath it
EMQX is installed and it is running. This part does the two things that make every later part shorter. First, a walk around the Dashboard — what each sidebar group is for, what the front page is actually counting, and where the version number hides — so that when a later part says “go to Access Control,” you already know where that is. Second, the mental model underneath the interface: topic, QoS, retained, will and session. Those five words are what you fall back on the day a device is fine on Monday and flaky on Tuesday.
+ fills exactly one level. The # takes every level that is left, and only at the endLearning the building before there is smoke in it
EMQX gathers everything involved in running a broker into one admin interface, and that interface is called the Dashboard. Who is connected right now. Whether a subscription actually took. Whether messages are going out at all. If you try to answer those from config files and log lines alone, you have thrown away about half of your debugging loop before you started. The Dashboard answers all three with a handful of cards.
So the point of this first half is not to configure anything. It is to build a map in your head: which sidebar group covers what, where the important numbers sit on the front page, and where to read the version and the license state. Every part after this one — authentication, ACL, listeners, connectors, monitoring, backup — opens by naming a menu path. You want to already know where that path starts, rather than hunting for it the first time something is broken.
The Dashboard is the CCTV room of a warehouse, not the warehouse. The forklifts keep moving, the deliveries keep arriving and the doors keep opening whether or not anybody is sitting at the monitors. You go in there to see what is happening, not to make it happen.
That is worth fixing early because it changes how you read a problem. The Dashboard is only a visual layer over EMQX. The broker sends and receives MQTT messages exactly as usual whether or not you have the Dashboard open. If the Dashboard will not load, your devices are very probably still talking to each other — you have lost the window, not the broker.
Two ways in, one page
The Dashboard listens on HTTP port 18083. There are two routes to it and both land on the same page.
-
Route 1
Through Home Assistant's sidebar
In Home Assistant's left sidebar, find the EMQX icon — that is the Ingress entry point — and click it. The Dashboard opens inside Home Assistant, and you did not have to know the host's address or the port.
-
Route 2
Straight at the port
Open the address in a browser yourself. The host part is whichever machine EMQX is running on:
http://<host>:18083This is the route you want when the host sits on a different subnet from the browser you are using, or when Home Assistant itself is the thing you are trying to debug.
-
First login
Sign in, then change the password before anything else
The first sign-in uses the default account:
admin/public— the factory default, and it is the same on every EMQX install in the worldEMQX prompts you to change it. Do that immediately, to a password of your own. Part 1 of this series covers the change itself. A broker left on the default admin password is a broker anybody who can reach port
18083can reconfigure. -
Then
Walk the sidebar end to end, once, with nothing broken
Go down the left column from Monitoring, through Access Control and Integration, all the way to System, and write one line for yourself on what each group is for. Then come back to Monitoring → Cluster Overview and take the whole picture in again. Ten minutes of this now is worth an hour later.
Cluster Overview, and where the version hides
The Dashboard home page is Cluster Overview. Two cards sit across the top half, and between them they carry the numbers you will check most often.
- Message Rate. How many messages the system takes in and sends out per second, shown live. This is the card that tells you whether anything is moving at all.
- Connections / Subscriptions / Topics. The current connection count, the total number of subscriptions, and the number of unique topics. These update the moment a connection changes, so plugging a device in and watching the connection count tick up is a perfectly good sanity check.
Below those sits a time-series chart, and it has a time range you can pick. Six ranges are offered: the past 1 hour, 6 hours, 12 hours, 1 day, 3 days or 7 days. That is how you tell a spike from a trend — a connection count that sags every night at the same hour looks like nothing on the 1 hour view and is obvious on the 7 day one.
The Nodes tab, and the version number
Switch to the Nodes tab and you get every EMQX instance in the cluster, one row each: name, status, uptime, connection count, version, Erlang process count, and memory and CPU usage. A node drawn in gray has stopped. Click a node's name and you open its detail view, with system paths, log paths and finer-grained statistics.
That version column is the answer to a question you will need more than once. This guide is written against EMQX 5.8.9, Open Source edition, and the most direct way to confirm what you are actually running is:
Monitoring → Nodes — the version column, in the row next to the node nameWrite it down somewhere. Menu paths and screen wording drift between EMQX versions, and the first thing anybody helping you will ask is which version you are on.
Publish, subscribe, and the broker in the middle
That is the tour. The rest of this part is the model underneath it, and it starts with the one idea the whole protocol is built on.
At the heart of MQTT is the publish/subscribe model, which splits the work into three roles. A publisher sends messages out under a topic. The broker — the EMQX you just toured — routes and filters every message. A subscriber subscribes only to the topics it cares about.
The point, and it is easy to read past: a publisher and a subscriber never need to know that the other exists. The only contract they share is the topic string. When the broker receives a message on a topic, it hands that data to every client currently subscribed to that topic, at the same time.
flowchart LR P["The living-room sensor
publishes to
living-room/temperature"] --> B["EMQX
the broker in the middle"] B --> S1["Home Assistant,
subscribed to
living-room/temperature"] B --> S2["A wall display,
subscribed to
living-room/temperature"] B --> S3["A logger, subscribed to
living-room/#"]
That is why adding a device, or adding a screen, disturbs none of the endpoints already running. The wall display in that picture can be unplugged for a fortnight and the sensor's code does not change by a character.
Worth pinning down: every endpoint is a client. A sensor is a client. A phone app is a client. Home Assistant is a client. There is no separate category of software that gets to be more important; the broker sees connections, and each connection brings its own parameters — keepalive, and how long its session is held — which the last part of this article covers.
If it helps, it is a noticeboard in a corridor rather than a phone call. You pin a note under the heading living room, temperature and walk off. You do not know whether three people read it or nobody did, and you do not wait to find out. Anybody who cares about that heading comes and reads it, and nobody has to be introduced to anybody.
Where one wrong character means silence
A topic is a UTF-8 string, split into levels by a forward slash /. In living-room/temperature, the level temperature sits under the level living-room. A publisher always sends to one specific topic. A subscriber, on the other hand, can use a wildcard to subscribe to many topics at once.
MQTT gives you exactly two wildcards, and they are not interchangeable.
The single-level wildcard: +
It matches exactly one level. Subscribing to living-room/+/temperature receives living-room/east/temperature and living-room/west/temperature. It does not receive living-room/east/upper/temperature, because east/upper is two levels where the + allows one.
The multi-level wildcard: #
It matches any number of levels, and it must come last. Subscribing to living-room/# receives living-room, living-room/temperature and living-room/east/upper/temperature alike — everything from that point down.
Two rules govern both of them, and breaking either one is the usual reason a subscription sits there receiving nothing.
- Wildcards are for subscribing only, never for publishing. You cannot publish to
living-room/+/temperature. A published message always names one exact topic. - Each wildcard fills a whole level, and in the case of
#, comes last.sensor/#/tempputs the#in the middle, which is not a legal filter.
flowchart TD M["One message, published to
living-room/east/upper/temperature"] M --> Q1{"Matched against the filter
living-room/+/temperature"} M --> Q2{"Matched against the filter
living-room/#"} Q1 -->|"no, + fills exactly one level"| N["This subscriber
receives nothing"] Q2 -->|"yes, # takes every level left"| Y["This subscriber
receives the message"]
+ filter and it ends in nothing delivered; the right branch is the # filter and it ends in delivery. The message itself is identical either way — the difference is entirely in what the subscriber asked for.| Subscription filter | Receives | Does not receive |
|---|---|---|
living-room/+/temperature |
living-room/east/temperatureliving-room/west/temperature |
living-room/east/upper/temperature — two levels where + allows one |
living-room/# |
living-roomliving-room/temperatureliving-room/east/upper/temperature |
Anything that does not start with the living-room level |
sensor/#/temp |
Nothing. This is not a legal filter | The # is in the middle, and it is only allowed at the end |
Think of a filing cabinet with drawers inside drawers. The + says “any one drawer at this depth, then keep going” — you still have to name what comes after it. The # says “this drawer and everything inside it, however deep it goes” — and once you have said that, there is nothing left to say, which is exactly why it has to be the last thing you write.
livingroom/temperature and a publisher on living-room/temperature will sit there in perfect silence, both of them apparently working, forever.QoS is a choice about consequences, not about quality
QoS stands for quality of service. It is a reliability setting carried by each message on its own, and it has three levels. Read the guarantees as they are written, because the shorthand people reach for — “higher is better” — is not what they say.
| QoS | Guarantee | Scenario |
|---|---|---|
| 0 | At most once, may be lost | Telemetry such as temperature and humidity, where losing one reading does no harm |
| 1 | At least once; duplicates are possible | Switch commands, though a duplicate copy may arrive |
| 2 | Exactly once, never duplicated | Payment and accounting logic, where a duplicate is never acceptable |
QoS 1 is the level that catches people out. Its guarantee is at least once, and the price of that guarantee is that duplicates are possible. A switch command sent at QoS 1 may genuinely arrive twice. If your receiving end toggles rather than sets, that is a light that ends up off when you asked for on.
flowchart TD A["Choosing the QoS
for one message"] --> B{"Does losing a single one
of these do any harm?"} B -->|"no"| Q0["QoS 0, at most once.
May be lost.
Temperature and humidity readings"] B -->|"yes"| C{"Is a duplicate copy
ever acceptable?"} C -->|"yes"| Q1["QoS 1, at least once.
Duplicates are possible.
Switch commands"] C -->|"no"| Q2["QoS 2, exactly once.
Never duplicated.
Payment and accounting logic"]
There is a cost attached, and it is the reason QoS 2 is not simply the right answer everywhere. The higher the QoS, the more negotiation and transfer it costs. QoS 0 is a good deal for everyday telemetry — a temperature reading that goes missing is replaced by the next one thirty seconds later. Keep 1 and 2 for devices whose control has to be guaranteed.
It is the difference between dropping a postcard in the box, sending something the postman keeps re-delivering until somebody signs for it, and a courier who checks a reference number both ways before handing it over. The postcard is not a worse product than the courier. It is a different arrangement, and you would not pay courier rates to tell your neighbor it is raining.
What survives a disconnection, and what does not
Five more terms. The first four explain almost every “why did the state come back wrong after a reboot” question you will ever ask; the fifth is about spreading load rather than surviving anything.
Session
A session is the state a client and the broker keep between them, and it is what makes QoS 1 and 2 work correctly at all. Which flag controls it depends on the protocol version the client speaks:
- MQTT 5.0 controls a session with
clean startand thesession expiry interval. - MQTT 3.1.1 uses the
clean sessionflag instead.
With a clean session, the session is discarded the moment the client disconnects. If you want the QoS 1 and 2 messages from the offline period to be delivered after the client reconnects, you want a persistent session rather than a clean one.
Keepalive
Keepalive is how long the client promises to go between control packets sent to the broker. If the broker hears nothing for longer than that, it treats the connection as dropped. When you find yourself wanting to adjust how quickly a flaky device is declared offline, keepalive is the number you are actually adjusting.
Retained messages
Mark a message as Retain and the broker stores the latest message for that topic. Any new subscriber to that topic receives it immediately, without waiting for the publisher to publish again. The cost is that only the latest message per topic is kept — a retained publish replaces the retained message before it, so this is a way to hold current state, not a history.
flowchart TD P["A publish to
living-room/humidity"] --> B["Delivered to every client
subscribed at that moment"] B --> R{"Was the message
marked Retain?"} R -->|"no"| N["Nothing is stored. A client that
subscribes later sees nothing
until the next publish"] R -->|"yes"| K["The broker keeps it as the latest
message for that topic, replacing
whatever it held before"] K --> L["A client that subscribes an hour later
receives it the moment it subscribes"]
To clear a retained message, publish an empty message to that topic. There is no delete button in the protocol; an empty payload on the same topic is the delete.
A retained message is the note stuck on the fridge door. Whoever walks into the kitchen next reads it, whether they were there when it was written or not. Write a new note and you take the old one down first — there is only ever one note on that door. Taking the note down and leaving nothing behind is the empty message.
Will messages
A client sets a will when it connects — a topic plus a payload. When that client drops unexpectedly, the broker publishes that will to the subscribers on its behalf, so everyone else knows straight away that its state has changed. It is the message you leave with the broker in advance, to be sent if you stop answering.
Will and Retain can be combined. When you set the will you can tick Retain as well, and then, besides publishing the will on an unexpected disconnect, the broker also stores it as a retained message — so a client that subscribes afterwards gets the offline state right away rather than waiting.
Shared subscriptions
A shared subscription takes this form:
$share/<group>/<topic> — the group name is yours to choose; the topic part is the ordinary filterIt spreads the message load evenly across the subscribers in one group — round_robin by default — instead of giving every one of them a duplicate copy. Within one group, each message is received by exactly one client, chosen by the strategy in force. Separate groups each receive their own copy. To raise throughput or add redundancy, split the work across several subscribers so each handles its own share.
Twenty minutes with the built-in WebSocket client
Everything above is words until you watch a message move. The Dashboard has a WebSocket test client built into it, which means you can be both the publisher and the subscriber without installing anything, wiring up a device, or writing a line of code. Run these four steps once and the terms stop being abstract.
-
Step 1
Open the WebSocket test client
In the EMQX Dashboard, go to the Diagnose group and find the built-in visual WebSocket client among the diagnose tools. It connects to your own broker as MQTT over WebSocket — it is a real client, not a simulation, and everything it does shows up in the counters on Cluster Overview.
-
Step 2
Subscribe to a topic
Add a subscriber and subscribe to a topic. Use this one, so it matches the examples above:
living-room/humidityDo not attach anything practical to it yet. The whole point of this step is to get a direct impression of subscribe, then receive.
-
Step 3
Publish a message to it
Switch to the publisher role, type
living-room/humidityas the topic and a number as the payload, then send it. Watch whether the subscriber receives it straight away. If it does, you have just done, by hand, the whole of what the broker exists to do. -
Step 4
Now try QoS and Retain
Change the publish QoS to
1and tick Retain, then send again. Now unsubscribe, and subscribe toliving-room/humiditya second time. The retained message arrives immediately, without anybody publishing anything new. That is the fridge-door note, and it is the behavior that explains why Home Assistant can show a sensor value seconds after a restart.
living-room/# in one subscriber, publish to living-room/east/upper/temperature, and watch it arrive. Then subscribe to living-room/+/temperature instead and publish the same topic again. Nothing arrives, and you have proved the rule to yourself rather than taking it on trust.Eight symptoms, and what each one actually means
Four of these are Dashboard problems and four are protocol problems, and telling those two apart is most of the work.
The Dashboard side
-
1
You can sign in, but the Dashboard will not open
Check which route you are on. Ingress from the Home Assistant sidebar and port
18083are two different paths to the same page, and they fail for different reasons. If the host is on another subnet from your browser, go direct withhttp://<host>:18083instead of through Ingress. -
2
You cannot find the version anywhere
Go to Monitoring → Nodes. The version column beside the node name is the version now running. On the install this guide is written against, expect 5.8.9.
-
3
A sidebar entry you read about is missing
Advanced features such as Delayed Publish and Alarms are not shown by default in the Open Source edition, and License and SSO need EMQX Enterprise. This is normal, not a fault. Before you go looking for a broken setting, check whether the feature belongs to the edition you are running.
-
4
The numbers on the home page look stuck
The Overview chart has a time range you can pick, so first make sure you are not looking at 7 days when you want the last few minutes. To start accumulating from scratch, use Reset Monitoring Data.
The protocol side
-
5
You subscribed to a wildcard topic and nothing arrives
Check two things in order. First, that
+and#are being used only on a subscription, never on a publish. Second, that they sit in a legal position:sensor/#/temp, for example, puts the#in the middle, and that is not valid. After that, compare the publisher's topic and the subscriber's filter character by character. -
6
Messages look like they are arriving twice
QoS 1 is by nature at least once, which means duplicates are possible — this is the protocol working as specified, not a bug in your setup. If duplicates are unacceptable, either move to QoS 2 or make the receiving end idempotent.
-
7
There is no earlier state after a restart
Retain was not set. Mark the state message as Retain so the broker holds the latest one, or switch to a persistent session so the QoS 1 and 2 data from the offline period is delivered afterwards. Those are two different mechanisms for two different needs: Retain serves whoever subscribes next, a persistent session serves one particular client coming back.
-
8
The session is not kept after a disconnect
Only QoS 1 and 2 messages on a session that is being kept are queued while the client is offline; plain QoS 0 is not kept at all. If you want data from the offline period delivered later, use a persistent session rather than a clean session — and publish that data at QoS 1 or 2, or there will be nothing to queue.
The ones that come up every time
Does the Dashboard have to be running for MQTT to work?
Where do I switch the language?
What is a “node” on the Overview page?
Why are some features missing from my sidebar?
Does MQTT always need a broker?
How does a retained message differ from an ordinary one?
Can a will message and Retain be used together?
With a shared subscription, who receives each message?
$share/<group>/ group, each message is received by exactly one client in the group, chosen by the strategy in force — round_robin by default. Separate groups each receive their own copy. That makes it a good fit for spreading load across several workers, or for adding redundancy.Can I publish to a topic containing + or #?
living-room/+/temperature, what you actually want is to publish to each of the real topics in turn, or to restructure the topic tree so one publish covers the case.Should I just use QoS 2 for everything and stop worrying?
Where to go from here
You have the map and the vocabulary. Now connect something real.
Part 3 takes the terms you have just used in a browser tab and makes them do work: a genuine first connection from a real client, and then authentication — deciding who is allowed to connect to your broker at all, which is the point where the default admin/public account stops being the only account that matters.
Part 2 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