An MQTT broker is the central server that receives every MQTT message and delivers it to the clients that asked for it. In this beginner's guide you will learn how the publish/subscribe model works, what an MQTT broker does behind the scenes, how topics and wildcards are structured, how MQTT 3.1.1 and 5.0 differ, and how to send your first messages from the command line and Python.
What Is MQTT?
MQTT is a lightweight messaging protocol designed for devices with limited bandwidth, power or processing capacity. It runs over TCP (and over WebSocket for browsers), has a tiny fixed header of just two bytes, and keeps a single long-lived connection open instead of polling. That makes it a natural fit for sensors, gateways, vehicles, smart meters and industrial machines, and equally useful for dashboards and backend services that consume their data.
MQTT is an open OASIS standard; the official home of the protocol is mqtt.org.
The Publish/Subscribe Model
In a classic request/response system such as HTTP, a client calls a specific server and waits for the answer. MQTT uses publish/subscribe instead:
- A publisher sends a message to a topic, for example
home/livingroom/temperature. - A subscriber tells the broker which topics it is interested in.
- The broker sits in the middle and forwards each message to every subscriber whose subscription matches the topic.
Here is the flow in words:
[Temperature sensor] --PUBLISH home/livingroom/temperature 22.5--> [MQTT broker]
[MQTT broker] --deliver--> [Mobile app] (subscribed to home/+/temperature)
[MQTT broker] --deliver--> [Analytics service] (subscribed to home/#)
Three kinds of decoupling make this powerful:
- Space: the sensor does not know the app's address, and the app does not know the sensor's. Both only know the broker.
- Time: with persistent sessions and retained messages, a subscriber does not have to be online at the exact moment a message is published.
- Scale: adding a tenth or a thousandth subscriber does not change the publisher at all.
What Does an MQTT Broker Do?
The MQTT broker is far more than a relay. It is responsible for several jobs at once.
Managing connections
Every client opens a TCP, TLS or WebSocket connection and sends a CONNECT packet with a client ID, optional username and password, a keep-alive interval and session flags. The broker authenticates the client, answers with CONNACK and then monitors the connection with PINGREQ/PINGRESP so it notices dead clients.
Keeping sessions
A session holds a client's subscriptions and any messages queued while it was offline. With a clean session the state is discarded on disconnect. With a persistent session the broker keeps it, so a device that drops off a mobile network receives what it missed when it returns. MQTT 5 replaces the old on/off flag with a session expiry interval measured in seconds.
Routing by topic
Each incoming PUBLISH is matched against all active subscriptions, including wildcard filters, and delivered to the matching clients. Routing is the heart of the MQTT broker, and it is why topic design matters.
Retained messages
A publisher can set the retain flag. The broker stores the last retained message per topic and immediately sends it to any new subscriber. This is ideal for state such as device/42/status = online or the latest configuration: a dashboard that opens later still sees the current value without waiting for the next update.
Last Will and Testament
When connecting, a client can register a "last will" message, for example device/42/status = offline. If the client disappears without a clean DISCONNECT (power loss, network failure, keep-alive timeout), the broker publishes the will on its behalf. Combined with a retained online message, this gives you reliable presence detection.
Quality of Service
MQTT defines three delivery guarantees, and the broker enforces them on both legs of the trip (publisher to broker, broker to subscriber):
| QoS | Name | Guarantee | Typical use |
|---|---|---|---|
| 0 | At most once | Fire and forget, no acknowledgement | Frequent sensor readings where a missed sample is fine |
| 1 | At least once | Acknowledged with PUBACK, may be duplicated | Most telemetry and events |
| 2 | Exactly once | Four-step handshake, no duplicates | Billing, commands that must not repeat |
The effective QoS for a subscriber is the lower of the publish QoS and the subscription QoS. For a deeper dive, read MQTT QoS Levels Explained.
MQTT Topics and Wildcards
Topics are UTF-8 strings split into levels by a forward slash. They are created on the fly: there is nothing to declare in advance.
factory1/line1/cnc1/temperature
factory1/line1/cnc1/spindle_rpm
factory1/line2/press3/status
home/kitchen/light/state
Good topic design reads from general to specific, avoids leading slashes and spaces, and does not put sensitive data in topic names. Topics starting with $ are reserved for the broker, for example ConnectMQ publishes $SYS/broker/connection/{clientId}/state.
Single-level wildcard: +
The plus sign matches exactly one level.
| Subscription filter | Matches | Does not match |
|---|---|---|
factory1/+/cnc1/temperature |
factory1/line1/cnc1/temperature |
factory1/line1/cell2/cnc1/temperature |
home/+/light/state |
home/kitchen/light/state |
home/light/state |
Multi-level wildcard:
The hash matches any number of levels, including zero, and must be the last character of the filter.
| Subscription filter | Matches |
|---|---|
factory1/# |
factory1, factory1/line1/cnc1/temperature, factory1/line2/press3/status |
factory1/line1/# |
Everything below factory1/line1 |
# |
Every non-$ topic the client is allowed to see |
Wildcards can only be used when subscribing; you always publish to a concrete topic. In industrial projects, a consistent hierarchy such as ISA-95 (enterprise, site, area, line, cell) becomes a Unified Namespace; see MQTT Sparkplug B and Unified Namespace for that pattern.
MQTT 3.1.1 vs MQTT 5.0
Most brokers and libraries today support both versions. MQTT 5.0 keeps the same model and adds features that make large deployments easier to run.
| Capability | MQTT 3.1.1 | MQTT 5.0 |
|---|---|---|
| Error feedback | Limited return codes | Reason codes on most acknowledgements (for example 0x97 Quota exceeded) |
| Session lifetime | Clean session on/off | Session expiry interval |
| Message lifetime | Not available | Message expiry interval |
| Load balancing | Not in the standard | Shared subscriptions $share/{group}/{filter} |
| Bandwidth savings | Full topic every time | Topic aliases |
| Request/response | Ad hoc conventions | Response topic and correlation data |
| Broker limits | Implicit | Announced in CONNACK (Maximum QoS, Receive Maximum, etc.) |
| Metadata | Payload only | User properties |
If you are starting a new project, use MQTT 5.0 unless a device library only supports 3.1.1. The full specification is published by OASIS at docs.oasis-open.org, and our overview of MQTT 5 features covers the practical highlights.
Hands-On: Try an MQTT Broker in Five Minutes
The quickest way to understand an MQTT broker is to use one. broker.connectmq.com offers a public area for anonymous testing.
| Protocol | Port | URL |
|---|---|---|
| MQTT (TCP) | 1883 | mqtt://broker.connectmq.com:1883 |
| MQTT over TLS | 8883 | mqtts://broker.connectmq.com:8883 |
| WebSocket | 8083 | ws://broker.connectmq.com:8083/mqtt |
| Secure WebSocket | 8084 | wss://broker.connectmq.com:8084/mqtt |
Rules for the public area: connect without a username or password, use a unique, non-empty client ID (an empty one is rejected), and remember that anything you publish is readable by anyone. Pick a random prefix such as connectmq/demo/<your-random-id>/ and never send personal or sensitive data. Limits are 64 KB per message, 10 messages per second and 10 subscriptions per client, and up to 8 topic levels.
Subscribe and publish with mosquitto
Install the Eclipse Mosquitto command-line clients, then open a terminal and subscribe with a wildcard. Replace k9x2 with your own random string:
mosquitto_sub -h broker.connectmq.com -p 1883 -i sub-k9x2 \
-t "connectmq/demo/k9x2/#" -v
In a second terminal, publish a reading:
mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-k9x2 \
-t "connectmq/demo/k9x2/room1/temp" -m '{"temp":22.5}' -q 1
The first terminal prints:
connectmq/demo/k9x2/room1/temp {"temp":22.5}
Now try a retained message. Publish a status with -r, then start a fresh subscriber that only uses the single-level wildcard:
mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-k9x2 \
-t "connectmq/demo/k9x2/room1/status" -m "online" -r
mosquitto_sub -h broker.connectmq.com -p 1883 -i sub2-k9x2 \
-t "connectmq/demo/k9x2/+/status" -v -C 1
The new subscriber receives online immediately, even though it connected after the message was sent. That is the broker storing and replaying the retained value. To clear it, publish an empty retained message with -r -n.
For encrypted traffic, switch to port 8883 and point the client at your system's CA certificates (for example --capath /etc/ssl/certs on Linux), and add -V mqttv5 to speak MQTT 5.0.
Python example with a last will
Install paho-mqtt 2.x with pip install paho-mqtt and run:
import json
import uuid
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
PREFIX = f"connectmq/demo/{uuid.uuid4().hex[:8]}"
CLIENT_ID = f"py-{uuid.uuid4().hex[:8]}"
def on_connect(client, userdata, flags, reason_code, properties):
print("Connected:", reason_code)
client.subscribe(f"{PREFIX}/#", qos=1)
client.publish(f"{PREFIX}/sensor1/status", "online", qos=1, retain=True)
client.publish(f"{PREFIX}/sensor1/temp", json.dumps({"temp": 21.8}), qos=1)
def on_message(client, userdata, msg):
print(f"{msg.topic} -> {msg.payload.decode()}")
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id=CLIENT_ID,
protocol=mqtt.MQTTv5,
)
client.will_set(f"{PREFIX}/sensor1/status", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, 1883, keepalive=30)
client.loop_forever()
The script registers a last will, announces itself as online with a retained message and publishes a temperature reading that it receives back through its own wildcard subscription. If you kill the process abruptly, the broker publishes offline on the status topic once the keep-alive expires.
Want to use the browser instead? The same broker accepts WebSocket connections on 8083 and 8084; see MQTT over WebSocket.
From Public Test Broker to Production
A public MQTT broker is perfect for learning and prototypes, but production needs private credentials, TLS and isolation from other users. ConnectMQ runs the broker for you: create one in the portal, receive a username, password and client ID, and connect over mqtts:// on port 8883. Your topics stay isolated from other tenants and from the public area, and the portal gives you a live topic explorer, dashboards and alert rules. Read What Is a Managed MQTT Broker? to compare hosting options.
Get Started with ConnectMQ
Sign up at app.connectmq.com to create your own MQTT broker on the free Serverless plan, no credit card required. You will be sending messages over TLS in a few minutes, with the same commands you just tried.
Frequently Asked Questions
What is an MQTT broker in simple terms?
An MQTT broker is a server that receives messages from publishing clients and forwards them to every client subscribed to a matching topic. Devices never talk to each other directly; they only talk to the broker.
What is the difference between an MQTT broker and an MQTT client?
A client is any device or application that publishes or subscribes, such as a sensor or a dashboard. The broker is the server in the middle that authenticates clients, keeps sessions and routes messages between them.
Which port does an MQTT broker use?
The standard ports are 1883 for plain MQTT and 8883 for MQTT over TLS. WebSocket connections commonly use separate ports; on broker.connectmq.com they are 8083 and 8084 with the path /mqtt.
Is there a free MQTT broker I can test with?
Yes. broker.connectmq.com has an anonymous public area for testing, and ConnectMQ's free Serverless plan gives you a private broker with credentials and TLS.
Should I use MQTT 3.1.1 or MQTT 5.0?
Use MQTT 5.0 for new projects because it adds reason codes, session and message expiry, shared subscriptions and topic aliases. Fall back to 3.1.1 only when a device library does not support version 5.



