The MQTT message expiry interval is an MQTT 5 property that tells the broker how many seconds a message stays valid. Once that time passes, the broker throws the message away instead of delivering it to a client that reconnects late. In this article you will learn exactly how expiry works, see it drop a stale command in a live test, and get a checklist for choosing expiry values in your own system.
The Problem: Commands That Arrive Too Late
Imagine a device subscribed with QoS 1 and a persistent session. An operator sends "open gate" while the device is offline. The broker queues the command because the session is still alive. Two hours later, the device reconnects over a flaky cellular link and the broker dutifully delivers "open gate". Nobody is standing at the gate any more.
Before MQTT 5 there was no protocol-level way to stop this. Developers added timestamps to payloads and hoped every device checked them. MQTT 5 moved the solution into the protocol with the message expiry interval, a property on the PUBLISH packet. It is one of the most practical MQTT 5 features for anyone controlling physical equipment.
How the MQTT Message Expiry Interval Works
The publisher sets the property in seconds, as a four-byte integer. The broker then applies these rules:
- When the message arrives, the broker records the time.
- If the message is queued for an offline subscriber with a persistent session, and the interval elapses before delivery, the broker discards it.
- When the broker does deliver the message, it rewrites the property to the remaining time. A message published with 60 seconds and delivered 45 seconds later arrives with an interval of 15.
- If the message is retained, the retained copy also expires. After the interval, new subscribers no longer receive it.
- If the property is absent, the message never expires, which is the MQTT 3.1.1 behaviour.
That fourth rule makes expiry useful for more than commands. A retained "maintenance mode active" banner can be set to expire on its own, so a forgotten flag does not linger for weeks.
| Message type | Suggested expiry | Reasoning |
|---|---|---|
| Actuator command (open, start, stop) | 5 to 60 s | Only valid while an operator is watching |
| Configuration push | Hours to days | Still useful when a device reconnects later |
| Telemetry to a persistent consumer | Minutes | Old readings lose value but may fill gaps |
| Retained temporary state | Duration of the state | Clears itself automatically |
| Alarm notification | Until it is no longer actionable | Avoid floods of outdated alarms |
Why Expiry Needs Persistent Sessions to Matter
Expiry only affects messages that wait somewhere. On a clean session with no stored state, a disconnected subscriber simply misses messages, so there is nothing to expire. Expiry matters in two places:
- Session queues. A client that connects with clean start set to false and a session expiry interval greater than zero has its QoS 1 and QoS 2 messages queued while offline. See MQTT session expiry interval, which explains how that queue is built.
- Retained storage. A retained message with an expiry is removed after the interval.
Note the difference between the two expiry settings: session expiry controls how long the broker remembers a client; message expiry controls how long a single message is worth delivering. You usually want both.
Testing Message Expiry on a Live Broker
We will use the public MQTT test broker at broker.connectmq.com, which supports MQTT 5 and sessions of up to 30 minutes. Use your own topic prefix.
Step 1: create a persistent session. Connect once with clean start disabled (-c), a 600-second session expiry (-x 600) and QoS 1, then stop with Ctrl+C:
mosquitto_sub -V mqttv5 -h broker.connectmq.com -p 1883 -i exp-dev-m4t9 \
-c -x 600 -q 1 -t connectmq/demo/m4t9/gate/cmd -v
Step 2: while the subscriber is offline, publish one command that expires in 10 seconds and one that lasts 5 minutes:
mosquitto_pub -V mqttv5 -h broker.connectmq.com -p 1883 -i exp-op-m4t9 -q 1 \
-t connectmq/demo/m4t9/gate/cmd -m 'open (10s)' \
-D publish message-expiry-interval 10
mosquitto_pub -V mqttv5 -h broker.connectmq.com -p 1883 -i exp-op-m4t9 -q 1 \
-t connectmq/demo/m4t9/gate/cmd -m 'report-status (300s)' \
-D publish message-expiry-interval 300
Step 3: wait at least 20 seconds, then reconnect with exactly the same command as in step 1. Only report-status (300s) arrives. The open command expired in the queue and was never delivered. Run step 2 and step 3 again quickly, within 10 seconds, and both messages arrive.
Setting Expiry in Python and Node.js
In paho-mqtt 2.x you attach a Properties object to the publish. The client must use MQTT 5:
import json
import uuid
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id=f"op-{uuid.uuid4().hex[:8]}",
protocol=mqtt.MQTTv5,
)
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_start()
props = Properties(PacketTypes.PUBLISH)
props.MessageExpiryInterval = 30 # seconds
cmd = {"action": "open", "issuedBy": "operator-3"}
info = client.publish("connectmq/demo/m4t9/gate/cmd", json.dumps(cmd), qos=1, properties=props)
info.wait_for_publish()
client.loop_stop()
client.disconnect()
On the receiving side, msg.properties contains MessageExpiryInterval with the remaining seconds, which a device can log or use to reject commands that are almost out of time.
With MQTT.js v5 the property sits in the publish options:
const mqtt = require("mqtt");
const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
clientId: "op-" + Math.random().toString(16).slice(2, 10),
protocolVersion: 5,
});
client.on("connect", () => {
client.publish(
"connectmq/demo/m4t9/gate/cmd",
JSON.stringify({ action: "close" }),
{ qos: 1, properties: { messageExpiryInterval: 30 } },
(err) => {
if (err) console.error(err);
client.end();
}
);
});
Pitfalls and Troubleshooting
Expiry is ignored entirely. Check that both publisher and broker connection use MQTT 5. A 3.1.1 client cannot send the property, and a 3.1.1 subscriber receives the message without it.
The message still arrives late. Expiry stops delivery from the broker's queue. It does not help if the message is already sitting in the device's own buffer or a gateway in between. Devices that execute commands should still compare a timestamp in the payload against their clock when safety matters.
Commands expire too quickly. Remember network and broker queuing latency. An expiry of 1 or 2 seconds may discard legitimate commands on slow links.
Retained message disappeared. If someone published the retained value with an expiry, it vanishes when the interval elapses. That is often intentional; check the publisher before assuming data loss. For retained basics, see MQTT retained messages.
Expiry versus QoS. QoS controls whether a message is delivered; expiry controls whether it is still worth delivering. A QoS 1 message with expiry can legitimately never arrive.
Combining Expiry with Other Safeguards
Message expiry is one layer of defence, not the whole design. For commands that move physical equipment, a robust setup usually combines several mechanisms:
- Expiry on every command so the broker drops stale ones from queues.
- A command ID and issue timestamp in the payload so the device can reject anything older than its own threshold, even if a gateway buffered it.
- No retain flag on commands, so a reboot never replays an old instruction.
- An acknowledgement topic where the device reports that it executed or rejected the command, ideally using the MQTT request response pattern.
- Interlocks on the device for anything safety-related. The network should never be the only thing preventing an unsafe action.
Think of expiry as the tool that keeps queues clean and makes late delivery rare, while the device-side checks make late execution impossible.
Message Expiry on ConnectMQ
ConnectMQ supports MQTT 5.0 with message expiry, session expiry, response topic and the other MQTT 5 properties on its MQTT 5.0 connections. For production, connect your operator tools and devices with credentials from the portal over TLS on port 8883 and set expiry on every command topic as a policy. A managed MQTT broker takes care of running the session store and the queues, so your team only has to choose sensible expiry values. The MQTT tutorial covers the remaining fundamentals.
Get Started with ConnectMQ
Create a broker for free at app.connectmq.com on the Serverless plan, no credit card required. Connect with MQTT 5, add an expiry to your first command, and test it against a device that reconnects late. Running it on a cloud MQTT broker means no servers to maintain while you experiment.
Frequently Asked Questions
What unit does the message expiry interval use?
Seconds, stored as a four-byte unsigned integer. If the property is not set, the message does not expire.
Does the subscriber see the original expiry value?
No. The broker sends the remaining time, so a subscriber can tell how much validity is left when the message arrives.
Does message expiry work with MQTT 3.1.1?
No. It is an MQTT 5 property. With MQTT 3.1.1 you need a timestamp in the payload that the receiver checks itself.
Can retained messages expire?
Yes. A retained message published with an expiry interval is removed from retained storage once the interval elapses.



