MQTT¶
!! abstract "Summary"
- **MQTT (Message Queuing Telemetry Transport)** is a lightweight **publish/subscribe** messaging protocol built for constrained devices and unreliable networks — exactly the conditions IoT devices usually live in.
- Unlike HTTP's request/response model, MQTT clients never talk to each other directly — every message passes through a central **broker**, which routes messages based on **topics** rather than destination addresses.
- It's an OASIS standard, currently at version 5.0 (with 3.1.1 still widely deployed), and runs over TCP — typically port `1883` unencrypted, or `8883` over TLS.
!!! tip "Think of MQTT as:"
"A radio station, not a phone call — publishers broadcast onto a named channel (topic), and anyone tuned in (subscribed) hears it, with no idea who else is publishing or listening."
If you are confused...
MQTT is an application-layer protocol that can run over any IP-capable network, cellular included. If you've read the 5G pages, one way to place MQTT: it's the layer that would sit above everything covered there, riding inside a PDU session's user-plane traffic once a UE actually has connectivity.
Overall Ideas¶
A Bit of History¶
MQTT wasn't designed for the modern IoT boom — it was invented in 1999 by Andy Stanford-Clark (IBM) and Arlen Nipper (Arcom, now Cirrus Link) to solve a much narrower problem: monitoring oil pipeline sensors over satellite links that were slow, expensive per byte, and frequently dropped.1 Those constraints shaped five design goals that still define MQTT today — simple to implement, support a range of QoS levels, lightweight and bandwidth-efficient, agnostic to the data it carries, and able to maintain continuous session awareness across an unreliable link.1
| Year | Milestone |
|---|---|
| 1999 | Invented at IBM, for oil pipeline SCADA monitoring over satellite |
| 2010 | Released royalty-free (v3.1) |
| 2011 | Client implementations contributed to the Eclipse Paho project |
| 2014 | v3.1.1 becomes an OASIS Standard |
| 2016 | Also published as ISO/IEC 20922:2016 |
| 2019 | v5.0 becomes an OASIS Standard |
Table 1. MQTT Milestones
Why It Caught On for IoT¶
- Minimal overhead — the fixed header can be as small as 2 bytes, so framing cost stays trivial even for a tiny payload.
- Bandwidth efficient — pub/sub means the network carries a message once per interested party (via the broker), not once per potential recipient.
- Flexible reliability — QoS 0–2 lets each individual message pick its own tradeoff, instead of forcing an entire application into one reliability model.
- Bidirectional & connection-aware — the broker can push data to a client at any time, and Keep Alive plus LWT mean a dropped connection gets detected and reported quickly, not silently.
- Scales to many-to-many — thousands of devices can publish to, or subscribe from, the same broker without any of them knowing about each other.
Built on TCP/IP¶
MQTT doesn't reinvent reliable delivery — it's an application-layer protocol that assumes an already-established TCP connection underneath, and leans on TCP for what TCP already does well: guaranteed delivery, in-order bytes, and retransmission of lost packets at the transport layer.2 That's a deliberate choice, not an oversight — it's exactly what lets MQTT's own framing stay so minimal. The tradeoff is that MQTT needs a working, connection-oriented network underneath it, which is part of why MQTT-SN exists — for links too constrained to justify a full TCP stack.
Publish/Subscribe Architecture¶
Figure 1. MQTT Publish/Subscribe Architecture3
Every MQTT connection is between one client and the broker — clients never connect directly to each other.1 This decoupling is the whole point:
- A publisher doesn't need to know who (or how many) subscribers exist.
- A subscriber doesn't need to know who's publishing.
- Publishers and subscribers don't even need to be connected at the same time — that's what retained messages and sessions exist to handle.
Client is a loose term
A "client" can be a publisher, a subscriber, or both at once — the same device very commonly does both (e.g., a smart thermostat publishing temperature readings while subscribing to a topic for setpoint commands).
Topics & Wildcards¶
Topics are hierarchical, UTF-8 strings with levels separated by / — for example, home/livingroom/temperature. There's no need to "create" a topic in advance; publishing or subscribing to one simply makes it exist.
Subscribers can use two wildcard characters when subscribing (never when publishing):2
| Wildcard | Matches | Example |
|---|---|---|
+ |
Exactly one topic level | home/+/temperature matches home/livingroom/temperature and home/kitchen/temperature, but not home/livingroom/sensor1/temperature |
# |
That level and all levels below it — must be the last character in the filter | home/# matches everything under home/, at any depth |
Table 2. MQTT Wildcards
Topic Name vs. Topic Filter
A Topic Name (used when publishing) must be fully specified and can't contain wildcards.2 A Topic Filter (used when subscribing) is the only place wildcards are allowed. Mixing these up is one of the most common beginner MQTT mistakes.
Quality of Service (QoS)¶
MQTT defines three delivery guarantees, chosen per-message by the publisher and capped per-subscription by the subscriber:2
| QoS | Guarantee | Handshake |
|---|---|---|
| 0 | At most once — "fire and forget" | PUBLISH only, no acknowledgment |
| 1 | At least once — may be delivered more than once | PUBLISH → PUBACK |
| 2 | Exactly once | PUBLISH → PUBREC → PUBREL → PUBCOMP (a four-packet handshake) |
Table 3. MQTT QoS
Why does QoS 2 need four packets instead of two?
QoS 1's simple ACK can still produce duplicates — if the PUBACK itself gets lost, the sender retransmits a message the receiver already got. QoS 2's extra PUBREC/PUBREL round trip exists specifically to let both sides confirm the exact message was received and processed exactly once, at the cost of more overhead — which is exactly why QoS 2 is recommended sparingly, and QoS 0 or 1 covers most IoT telemetry just fine.
Retained Messages & Last Will and Testament¶
Two features specifically address MQTT's async nature — that publishers and subscribers aren't guaranteed to be online at the same time:
- Retained messages — when a publisher sets the
retainflag, the broker stores that message (only the single most recent one per topic) and immediately delivers it to any client that subscribes afterward, rather than making them wait for the next publish.4 - Last Will and Testament (LWT) — a client can register a message, at connect time, that the broker will publish on its behalf if that client disconnects ungracefully (network drop, timeout) rather than cleanly (a proper
DISCONNECTpacket).5
Combining both
A common pattern: a device publishes a retained "online" message to devices/sensor001/status on connect, and registers "offline" as its LWT on the same topic. Any client subscribing to that topic — at any time — immediately learns the device's current status, whether it's currently connected or not.
Sessions & Keep Alive¶
- Clean Session (MQTT 3.1.1) / Session Expiry Interval (MQTT 5.0) — controls whether the broker discards a client's subscriptions and undelivered QoS ½ messages when it disconnects, or preserves them for when it reconnects.6
- Keep Alive — the client tells the broker, at connect time, the longest gap it will allow between packets. If nothing else is sent, the client pings the broker with
PINGREQ(answered withPINGRESP) to prove the connection is still alive. If the broker hears nothing for 1.5× the Keep Alive interval, it closes the connection and fires the client's LWT if one was set.6
The 18-hour ceiling
Keep Alive is encoded as a 16-bit number of seconds, which caps the maximum interval at exactly 18 hours, 12 minutes, 15 seconds — a nice example of a protocol limit falling straight out of its wire format.6
MQTT Control Packets¶
Figure 2. MQTT Packet Size7
Every MQTT interaction is built from one of 15 Control Packet types, each identified by a 4-bit type value in the packet's fixed header:2
| Type | Packet | Purpose |
|---|---|---|
| 1 | CONNECT |
Client → broker, opens a session |
| 2 | CONNACK |
Broker → client, acknowledges CONNECT |
| 3 | PUBLISH |
Carries an application message |
| 4 | PUBACK |
QoS 1 acknowledgment |
| 5 | PUBREC |
QoS 2 handshake, step 2 |
| 6 | PUBREL |
QoS 2 handshake, step 3 |
| 7 | PUBCOMP |
QoS 2 handshake, step 4 |
| 8 | SUBSCRIBE |
Client → broker, requests one or more topic filters |
| 9 | SUBACK |
Broker → client, acknowledges SUBSCRIBE |
| 10 | UNSUBSCRIBE |
Client → broker, removes a subscription |
| 11 | UNSUBACK |
Broker → client, acknowledges UNSUBSCRIBE |
| 12 | PINGREQ |
Client → broker, Keep Alive ping |
| 13 | PINGRESP |
Broker → client, Keep Alive response |
| 14 | DISCONNECT |
Graceful connection close (suppresses the LWT) |
| 15 | AUTH |
Extended authentication exchange — MQTT 5.0 only |
Table 4. MQTT Control Packet Types and their type values
Type 15 wasn't always AUTH
In MQTT 3.1.1, value 15 was simply reserved and unused. MQTT 5.0 is what gave it a job — AUTH, for extended authentication exchanges (e.g., challenge/response mechanisms like SCRAM) that don't fit into a plain username/password CONNECT.
Overall Flow¶
Figure 3. MQTT Protocol Flow Overview8
Putting everything above together, a typical MQTT session looks like this:8
- Connect — client sends
CONNECT, broker repliesCONNACK. - Subscribe — client sends
SUBSCRIBEwith one or more topic filters, broker repliesSUBACKconfirming the granted QoS for each. - Exchange messages — as matching messages are published (by this client or any other), the broker forwards them as
PUBLISHpackets, running whatever QoS handshake that message's QoS level requires. - Stay alive — during any quiet period,
PINGREQ/PINGRESPkeep the connection alive. - Disconnect — client sends
DISCONNECTto close cleanly, suppressing its LWT.
This is why nothing on this page needed a separate diagram for "how you connect" versus "how you publish" — it's all one continuous session, built entirely from the packets and concepts already covered above.
Example¶
The fastest way to actually feel how MQTT works is two terminals and a local broker — no cloud account, no IoT device required.
Install Mosquitto — an open-source MQTT broker that also ships the mosquitto_pub/mosquitto_sub CLI clients:
Mosquitto starts automatically on install, listening on localhost:1883.
Terminal 1 — subscribe and leave it running:
Terminal 2 — publish a message:
You should see the message appear in Terminal 1 immediately — that round trip is the entire flow described above: CONNECT/CONNACK for both clients, SUBSCRIBE/SUBACK for Terminal 1, and a PUBLISH the broker forwarded the moment it arrived.
Try building on this
- Retained messages:
mosquitto_pub -h localhost -t "kuoquo/test" -m "I persist" -r, then open a third terminal and subscribe fresh — the message arrives immediately, without Terminal 2 publishing again. - Wildcards: subscribe with
mosquitto_sub -h localhost -t "kuoquo/#" -v, then publish tokuoquo/test/sub— it still arrives, even though nothing subscribed to that exact topic. - QoS: add
-q 2to the publish command to force the four-packet QoS 2 handshake — delivery looks identical from the terminal, but a packet capture (e.g., Wireshark onlo, filtered onmqtt) would showPUBREC/PUBREL/PUBCOMPhappening underneath.
MQTT 5.0: What's New¶
MQTT 5.0 (2019) added several features on top of 3.1.1,9 without changing the core pub/sub model:2
- Reason Codes — nearly every acknowledgment packet can now carry a specific reason for success or failure, instead of MQTT 3.1.1's mostly binary outcomes.
- User Properties — arbitrary key/value metadata attachable to most packets.
- Session Expiry Interval & Message Expiry Interval — finer-grained, explicit control (in seconds) over how long a session or an individual message survives, including how long a retained message sticks around.10
- Topic Alias — lets a client substitute a short integer for a long topic name after the first use, shrinking repeated
PUBLISHoverhead. - Shared Subscriptions — multiple clients can subscribe to the same topic filter as a group, with the broker load-balancing messages across them rather than delivering to every member.
- Request/Response pattern support — standardized
Response TopicandCorrelation Dataproperties, making RPC-style interactions over MQTT's pub/sub model much cleaner to implement.
MQTT-SN: For Constrained Devices¶
MQTT-SN (MQTT for Sensor Networks) is a related but separate specification — not simply "MQTT over UDP" — designed for devices too constrained even for MQTT's already-light TCP footprint, such as battery-powered sensors on low-power radio links. It replaces long text-based topic names with short pre-registered topic IDs, and typically talks to the rest of an MQTT network through a gateway that translates between MQTT-SN and standard MQTT.
Best Practices¶
Guidance drawn from how the concepts above actually get used in production:
- Design topics deliberately — use a consistent hierarchy (e.g.,
<location>/<device-type>/<device-id>/<measurement>), and treat topic names as a stable API, not an afterthought. Changing them later breaks every subscriber depending on the old names. - Default to QoS 0, not QoS 2 — QoS 2's four-packet handshake has real overhead. Reach for QoS 1 or 2 only when a specific message actually needs the stronger guarantee — a command is a better candidate than a repeating sensor reading, where the next update arriving soon anyway makes an occasional drop harmless.
- Set an LWT on every long-lived client — it's the only reliable way subscribers learn a device went offline unexpectedly, rather than assuming silence just means "nothing to report."
- Use retained messages for state, not one-off events — a device's current status is a good fit; a discrete event like a button press generally isn't, since a new subscriber would receive that "stale" retained event as if it just happened.
- Use TLS (
8883) outside a trusted local network — plain MQTT (1883) sends everything, including usernames and passwords, in cleartext. - Keep payloads small and structured — JSON is common and interoperable, but for genuinely constrained devices, a compact binary format (CBOR, Protobuf) can meaningfully cut bandwidth and parsing cost.
- Set a sensible Keep Alive, not the maximum — too long delays detecting a dead connection (and firing the LWT); too short wastes bandwidth on pings. Tens of seconds to a few minutes covers most IoT use cases.
Useful Resources¶
- MQTT Version 5.0 — OASIS Standard — the current specification
- MQTT Version 3.1.1 — OASIS Standard — still the most widely deployed version
- HiveMQ — MQTT Essentials, a ten-part series covering these concepts in more depth
- ShareTechnote — IoT: MQTT — byte-level packet structure and a captured protocol sequence
- Mosquitto — the open-source broker used in the example above
-
HiveMQ. (n.d.). Introducing the MQTT protocol — MQTT Essentials: Part 1. https://www.hivemq.com/blog/mqtt-essentials-part-1-introducing-mqtt/ ↩↩↩
-
OASIS. (2019). MQTT Version 5.0. OASIS Standard. https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html ↩↩↩↩↩↩
-
MQTT.org. (n.d.). MQTT: The standard for IoT messaging. https://mqtt.org/ ↩
-
HiveMQ. (n.d.). What are retained messages in MQTT? — MQTT Essentials: Part 8. https://www.hivemq.com/blog/mqtt-essentials-part-8-retained-messages/ ↩
-
HiveMQ. (n.d.). What is MQTT Last Will and Testament (LWT)? — MQTT Essentials: Part 9. https://www.hivemq.com/blog/mqtt-essentials-part-9-last-will-and-testament/ ↩
-
HiveMQ. (n.d.). What is MQTT Keep Alive and client take-over? — MQTT Essentials: Part 10. https://www.hivemq.com/blog/mqtt-essentials-part-10-alive-client-take-over/ ↩↩↩
-
HiveMQ. (n.d.). MQTT packets: A comprehensive guide. https://www.hivemq.com/blog/mqtt-packets-comprehensive-guide/ ↩
-
Ryu, J. (n.d.). IoT – MQTT. ShareTechnote. https://www.sharetechnote.com/html/IoT/App_Protocol_MQTT.html#Protocol_Sequence ↩↩
-
OASIS. (2014). MQTT Version 3.1.1. OASIS Standard. https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html ↩
-
HiveMQ. (n.d.). MQTT session expiry and message expiry intervals — MQTT 5 Essentials: Part 4. https://www.hivemq.com/blog/mqtt5-essentials-part4-session-and-message-expiry/ ↩


