MQTT message ordering is guaranteed, but only within specific limits that many developers never read. Messages from one publisher on one topic at one QoS level arrive in order; almost everything beyond that is not promised. This article explains exactly what the specification guarantees, the common situations where messages appear out of order, and how to add sequence numbers so your consumers can detect and handle it.
What the MQTT Specification Guarantees
Both MQTT 3.1.1 and MQTT 5 define an "Ordered Topic". By default a broker must treat every topic as ordered, which leads to this guarantee:
Messages published by one client to one topic, at the same QoS level, are delivered to each subscriber in the order they were published.
The spec also requires that when a client resends unacknowledged QoS 1 and QoS 2 messages after a reconnect, it does so in the original order, and that acknowledgements are sent in the order the packets were received.
Read that guarantee carefully and the gaps become obvious. It says nothing about:
- Messages from different publishers on the same topic.
- Messages from one publisher on different topics.
- Messages from one publisher on the same topic but at different QoS levels.
- What your application does after the client library hands over the message.
Where MQTT Message Ordering Breaks Down
| Situation | Ordering guaranteed? | Why |
|---|---|---|
| One publisher, one topic, same QoS | Yes | The Ordered Topic rule |
| One publisher, two topics | No | Each topic is ordered independently |
| Two publishers, one topic | No | No global clock; arrival order at the broker wins |
| Same topic, mixed QoS 0 and QoS 1 | No | Different delivery paths and retries |
| QoS 0 over a dropped connection | Order kept, but gaps | Lost messages are not resent |
| QoS 1 with redelivery | Duplicates can appear | A resent message may follow newer ones it already preceded |
| Shared subscription | No global order | Messages are spread across consumers |
| Multithreaded consumer | No | Your worker pool reorders processing |
Two of these deserve a closer look.
Shared subscriptions. With $share/group/topic, the broker distributes messages across several consumers for load balancing. Each consumer sees a subset, and they process in parallel, so there is no meaningful global order. If ordering per device matters, design so each device's messages always reach the same consumer, or reorder downstream. Our MQTT shared subscriptions guide covers the trade-off.
Redelivery and duplicates. QoS 1 is at-least-once. If an acknowledgement is lost, the sender retransmits with the DUP flag set. A subscriber may then see message 5 again after it has already processed 6 and 7. This is a duplicate rather than a true reorder, but to naive code it looks like time went backwards. The fix is covered in handling MQTT duplicate messages.
Demonstrating Ordering on a Public Broker
The quickest experiment uses the public MQTT test broker at broker.connectmq.com. Note its limit of 10 messages per second per client, so the publisher below throttles itself.
Subscriber:
mosquitto_sub -h broker.connectmq.com -p 1883 -i ord-sub-h5d3 -q 1 \
-t 'connectmq/demo/h5d3/#' -v
Publisher, one topic, QoS 1, numbered messages:
for i in $(seq 1 20); do
mosquitto_pub -h broker.connectmq.com -p 1883 -i ord-pub-h5d3 -q 1 \
-t connectmq/demo/h5d3/seq -m "$i"
sleep 0.2
done
You will see 1 to 20 in order. Note that each mosquitto_pub call is a separate connection, so the sequential loop is what keeps order here. Now run two loops in parallel on two different topics, seq-a and seq-b. Each topic stays in order, but lines from the two topics interleave unpredictably. Nothing is wrong: cross-topic order is simply not part of the contract.
Adding Sequence Numbers
When order matters to your application, carry it in the payload. A monotonic sequence number per publisher, plus a timestamp, lets consumers detect gaps, duplicates and stale messages. Here is a complete paho-mqtt 2.x publisher and consumer pair:
import json
import time
import uuid
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
TOPIC = "connectmq/demo/h5d3/pump-07/telemetry"
last_seq = {} # device -> highest sequence seen
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(TOPIC, qos=1)
def on_message(client, userdata, msg):
data = json.loads(msg.payload)
dev, seq = data["device"], data["seq"]
prev = last_seq.get(dev)
if prev is not None and seq <= prev:
print(f"stale or duplicate seq={seq} (last={prev}), dropped")
return
if prev is not None and seq > prev + 1:
print(f"gap: missing {prev + 1}..{seq - 1}")
last_seq[dev] = seq
print(f"apply seq={seq} value={data['value']}")
sub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"ord-c-{uuid.uuid4().hex[:6]}")
sub.on_connect = on_connect
sub.on_message = on_message
sub.connect(BROKER, 1883, keepalive=30)
sub.loop_start()
time.sleep(1)
pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"ord-p-{uuid.uuid4().hex[:6]}")
pub.connect(BROKER, 1883, keepalive=30)
pub.loop_start()
for seq in [1, 2, 3, 5, 4, 6]: # 4 deliberately sent late
body = {"device": "pump-07", "seq": seq, "ts": time.time(), "value": 20 + seq}
pub.publish(TOPIC, json.dumps(body), qos=1).wait_for_publish()
time.sleep(0.2)
time.sleep(2)
for c in (pub, sub):
c.loop_stop()
c.disconnect()
The consumer applies 1, 2, 3, reports a gap before 5, drops the late 4 as stale, then applies 6. Whether to drop, buffer or accept late messages depends on the data, which brings us to strategy.
Choosing an Ordering Strategy
- Last value wins (state). For readings such as temperature, drop anything older than what you have. The code above does this.
- Reorder buffer (event streams). Hold messages briefly and release them in sequence order, giving up on gaps after a timeout. Useful when every event matters, such as counters.
- Idempotent upserts (storage). Write each message keyed by device and sequence or timestamp. The database ends up correct regardless of arrival order.
- One topic per ordered stream. If commands must be applied in order, publish them on a single topic from a single client at a single QoS.
- Avoid mixing QoS on one stream. Pick one QoS per topic.
Ordering Across Reconnects and Offline Periods
Reconnects are where ordering assumptions are most often tested. When a subscriber with a persistent session comes back, the broker first resends any messages that were in flight and unacknowledged, then delivers the messages queued while the client was offline, then continues with live traffic. Within one topic from one publisher the order still holds, but the consumer suddenly receives a burst of older messages followed by current ones.
Two things help here. First, keep processing per device sequential, so a burst is applied in order rather than raced by parallel workers. Second, use timestamps in the payload to decide how to present catch-up data: a dashboard may want to skip straight to the latest value, while a historian should store every reading with its original time. If queued messages are only useful for a limited time, an MQTT 5 message expiry keeps very old ones out of the burst entirely.
On the publishing side, devices that buffer readings locally during a network outage should send the buffer in sequence order before resuming live publishing, and on the same topic and QoS as normal traffic.
Ordering Pitfalls and Troubleshooting
Sequence resets after reboot. A device that restarts at seq 1 looks like it is sending stale data. Include a boot ID or epoch counter with the sequence, or persist the counter in flash.
Clock-based ordering fails. Device clocks drift and sometimes reset to 1970. Prefer sequence numbers for ordering and timestamps for display.
Order lost inside your app. Async handlers, thread pools and queues in the consumer reorder freely. Process each device's stream on a single worker, or partition by device ID.
In-flight window effects. MQTT 5's receive maximum lets several QoS 1 messages be in flight at once. Ordering on the wire still holds, but retries under packet loss make duplicates more likely. See the MQTT receive maximum article for tuning.
Ordering on a Managed Broker
ConnectMQ follows the MQTT ordering rules per topic for MQTT 3.1.1 and 5.0 clients. Shared subscriptions on the paid Serverless plan give you horizontal scaling for consumers, with the usual ordering trade-off described above. Running a hosted MQTT broker does not change the protocol guarantees, but it removes the operational work so you can concentrate on consumer design. For the wider protocol context, see the MQTT tutorial.
Get Started with ConnectMQ
Create a broker on the free Serverless plan at app.connectmq.com, no credit card required. Run the sequence-number example against your own ConnectMQ credentials and watch the stream in the portal's live topic explorer, then move it to production on a cloud MQTT broker that scales with your fleet.
Frequently Asked Questions
Does MQTT guarantee message order?
Yes, for messages from one client on one topic at the same QoS. There is no guarantee across topics, publishers or mixed QoS levels.
Does QoS 2 guarantee ordering?
QoS 2 guarantees exactly-once delivery per hop. Ordering still follows the same per-topic, per-publisher rule.
Why do I see an older message after a newer one?
Usually a QoS 1 redelivery, a second publisher, or parallel processing in your consumer. Sequence numbers make it easy to tell which.
Are shared subscriptions ordered?
Not globally. Each message goes to one member of the group, and members process in parallel.



