MQTT
MQTT is a publish/subscribe messaging protocol designed for devices with very little of everything: memory, bandwidth, and reliable connectivity. Its fixed header is two bytes. A minimal publish costs a handful of bytes over TCP. It was built in the 1990s for satellite-linked oil pipeline telemetry, and those constraints — expensive links, unreliable connections, tiny devices — turn out to describe most of IoT.
The protocol's shape follows from three decisions. Clients never talk to each other; a broker sits in the middle, so devices need exactly one outbound connection and no inbound reachability. Messages are addressed to hierarchical topics rather than recipients, so producers and consumers are fully decoupled. And because devices drop off constantly, the protocol has built-in machinery for it: last will messages announce ungraceful disconnects, retained messages give new subscribers immediate state, and persistent sessions queue messages for absent clients.
TL;DR
- Pub/sub through a broker — devices need one outbound TCP connection and no open ports.
- Topics are hierarchical strings (
site/floor2/room3/temp) with wildcards+(one level) and#(multi-level). - QoS 0 at most once, QoS 1 at least once (duplicates possible), QoS 2 exactly once (expensive).
- QoS 1 plus idempotent consumers is the right default; QoS 2 is rarely worth its cost.
- Retained messages give a new subscriber the last known value immediately.
- Last Will and Testament is published by the broker when a client disconnects ungracefully — the standard presence mechanism.
- Never publish unencrypted. MQTT over TLS on 8883, with per-device credentials or client certificates.
- MQTT 5 adds properties, shared subscriptions, reason codes, and message expiry.
Quick Example
A device that publishes telemetry and accepts commands — the canonical IoT shape:
Four production essentials appear there and are routinely omitted in tutorials: TLS, a last will, subscribing inside on_connect (so reconnects restore subscriptions), and idempotent command handling.
Core Concepts
The broker model
The broker is also the operational single point of failure and the natural place for authentication, authorization, and observability. Production deployments cluster it (EMQX, HiveMQ, VerneMQ) or use a managed service (AWS IoT Core, Azure IoT Hub, HiveMQ Cloud).
Topics
Topics are created implicitly by publishing — there's no registration step. That flexibility means topic design is entirely your discipline, and getting it wrong is expensive to change once thousands of devices are deployed.
Good structure: {domain}/{device-id}/{direction}/{detail}, most general to most specific, with the identity high in the tree so authorization rules can be written as prefixes. Avoid leading slashes, spaces, and $ (reserved for broker system topics like $SYS/).
Quality of Service
QoS is negotiated per hop, not end to end: publishing at QoS 2 to a broker doesn't mean a QoS 0 subscriber gets exactly-once delivery. The effective QoS is the minimum of the publish QoS and the subscription QoS.
The practical recommendation is QoS 1 with an idempotency key in the payload. QoS 2 costs four round trips and per-message broker state, and duplicate handling is something a robust consumer needs regardless. See Idempotency.
Retained messages
The broker stores the last retained message per topic and delivers it immediately to any new subscriber. This is how a dashboard shows current state on load instead of waiting for the next 30-second telemetry cycle.
Retain is for state, not events. Retaining "temperature is 22°C" is right; retaining "button pressed" means every future subscriber receives a stale button press. Clear a retained message by publishing an empty payload with retain=True.
Last Will and Testament
The client registers this at connect time. If the connection drops without a clean DISCONNECT — power loss, network failure, crash — the broker publishes it on the client's behalf. Combined with a retained "online" message on connect, this gives you accurate presence tracking that costs nothing while the device is healthy.
Detection latency is governed by the keep-alive: the broker declares a client dead after 1.5 × keep-alive with no traffic. A 60-second keep-alive means up to 90 seconds before the will fires.
Sessions and keep-alive
A persistent session (clean_session=False, or MQTT 5's sessionExpiryInterval) makes the broker remember the client's subscriptions and queue QoS 1 and 2 messages while it's offline. Essential for devices that sleep or have intermittent connectivity — and a memory cost on the broker that you should bound with an expiry.
Keep-alive is a tradeoff: shorter means faster failure detection and more radio wake-ups, which matters enormously on a battery device. Cellular and satellite links often push it to several minutes.
MQTT 5 additions
- User properties — arbitrary key/value headers, so metadata doesn't have to live in the payload.
- Response topic and correlation data — proper request/response over pub/sub.
- Shared subscriptions (
$share/group/topic) — load-balance a topic across consumers, which MQTT 3.1.1 couldn't do. - Reason codes — the broker explains why it rejected something instead of just closing the connection.
- Message expiry — a message the broker drops if undelivered within N seconds, preventing a reconnecting device from being flooded with stale data.
- Topic aliases — send a long topic once, then reference it by an integer.
Shared subscriptions and message expiry alone justify MQTT 5 for most new server-side work; broker and client support is now widespread.
Security
⚠️ Warning: MQTT has no security of its own. Plain MQTT on port 1883 sends credentials and payloads in cleartext. Public broker scans routinely find thousands of unauthenticated brokers exposing industrial and home systems.
The baseline:
Topic ACLs are the control that matters most and is most often skipped. Without them, any authenticated device can subscribe to # and read the entire fleet's traffic, or publish commands to any other device. Scope every device to its own subtree.
For constrained devices, TLS costs flash (a stack is tens of KB), RAM (handshake buffers), and handshake time. Session resumption and pre-shared keys reduce it, and hardware crypto acceleration on parts like the ESP32 makes it entirely practical. Cost is not a reason to ship without it. See IoT Security.
Best Practices
Design the topic hierarchy before deploying
Topics are effectively an API, and they're the hardest thing to change once devices are in the field. Put identity high in the tree so ACLs are prefix rules, keep levels meaningful, and separate telemetry from commands from status so subscribers can filter precisely.
Subscribe inside the connect callback
Subscribing once at startup means a device that reconnects silently stops receiving commands — a failure that's invisible until someone notices the device ignoring them.
Use QoS 1 and make consumers idempotent
QoS 1 covers nearly every case at a fraction of QoS 2's cost. Include a message ID in the payload, track recently seen IDs, and skip duplicates. This is the same discipline any at-least-once system needs.
Retain state, not events
Retained messages are a "current value" cache. A retained event message is delivered to every future subscriber as though it just happened.
Keep payloads small and versioned
JSON is convenient for development and verbose on the wire. For high-frequency or bandwidth-constrained links, use CBOR, MessagePack, or Protobuf. Whatever the format, include a schema version field from the first release — a fleet will run several firmware versions simultaneously, and the server must handle all of them.
Tune keep-alive to the connectivity and power budget
Short keep-alives detect failures quickly and wake the radio often. On a battery device, each wake-up is measurable. Match the interval to how quickly you actually need to detect a dead device.
Implement reconnection with exponential backoff and jitter
Devices reconnect in herds after a broker restart or a network blip. Backoff with jitter prevents a thundering herd that keeps the broker from recovering. Most client libraries have this built in — verify it's enabled rather than assuming.
Bound persistent session growth
An offline device with a persistent session accumulates queued messages. Set message expiry (MQTT 5) and a session expiry interval so a device that's been off for a month doesn't reconnect to ten thousand stale messages.
Common Mistakes
Publishing without TLS
Subscribing only once at startup
Using a wildcard when publishing
Sharing one credential across the fleet
Retaining events
Reconnecting without backoff
FAQ
MQTT or HTTP for IoT?
MQTT when devices publish frequently, when the server must push to devices, when bandwidth or power is constrained, or when connectivity is intermittent — a persistent connection avoids per-request handshakes and lets the server reach devices behind NAT. HTTP when interactions are infrequent, request/response-shaped, or need to traverse restrictive corporate proxies that only allow port 443. Many fleets use both: MQTT for telemetry and commands, HTTPS for firmware downloads and provisioning.
MQTT or CoAP?
CoAP is UDP-based, REST-shaped, and even lighter, which suits very constrained devices and multicast discovery. MQTT has far broader tooling, cloud support, and library maturity, and its persistent connection handles NAT better. Choose CoAP for extremely constrained or mesh networks; MQTT for essentially everything else in practice.
Which broker should I use?
Mosquitto for development, small deployments, and edge gateways — tiny, simple, ubiquitous. EMQX, HiveMQ, or VerneMQ for clustered production deployments with tens of thousands of devices. AWS IoT Core or Azure IoT Hub when you want device identity, provisioning, and cloud integration managed for you. NanoMQ for resource-constrained edge devices.
How many devices can one broker handle?
Mosquitto on modest hardware handles tens of thousands of connections; clustered brokers handle millions. The binding constraint is usually message rate and fan-out rather than connection count — one message to a topic with 10,000 subscribers is 10,000 deliveries. Model your actual fan-out before sizing.
Can MQTT work over WebSockets?
Yes, and it's the standard way to reach browsers. Brokers expose MQTT over WebSocket (commonly port 8083, or 8084 with TLS), and libraries like MQTT.js connect directly from a web page. This is how live dashboards get real-time updates without polling. See WebSockets.
How do I handle firmware updates over MQTT?
Publish availability and metadata over MQTT — version, size, hash, URL — and download the image over HTTPS. MQTT is designed for small messages, and pushing a megabyte through it works poorly and hurts every other client on the broker. The command channel is MQTT; the bulk transfer is not. See OTA Firmware Updates.
Related Topics
- IoT Security — TLS, provisioning, and per-device credentials
- OTA Firmware Updates — Using MQTT as the control channel
- ESP32 — The most common MQTT client platform
- Microcontrollers — What's running the client
- Message Queues — Broker-based messaging in general
- Event-Driven Architecture — The pattern MQTT implements
- Idempotency — Required for QoS 1 consumers
- WebSockets — MQTT in the browser
- Kafka — Where fleet telemetry usually lands next