To delete retained message MQTT entries you do not need a broker admin tool: you publish a zero-length payload with the retain flag set to the same topic, and the broker drops the stored value. This guide shows that technique from the command line, Python, JavaScript and a browser client, plus how to find and bulk-clear stale retained topics safely.
Why Retained Messages Get Stuck
A retained message is the last message a broker stores for a topic so that any new subscriber immediately receives the current value. That is exactly what you want for a device status, a configuration document or the latest sensor reading. If you are new to the feature, the guide to MQTT retained messages explains the mechanics in detail.
The trouble starts when the stored value stops being true. Typical situations:
- A device was decommissioned, but its last
onlinestatus keeps appearing in dashboards. - A test publish with the retain flag set during development now pollutes a production topic tree.
- A configuration topic was renamed, and the old one still delivers outdated settings to new clients.
- A firmware bug published malformed JSON with retain, and every reconnecting subscriber crashes on it.
Because the broker replays the retained value on every new subscription, the problem never goes away by itself. You have to replace the stored value or remove it.
How to Delete a Retained Message in MQTT
The MQTT specification defines the rule in one sentence: a PUBLISH packet with the RETAIN flag set to 1 and a zero-length payload removes any retained message for that topic, and the broker does not store a new one. The empty message is also forwarded to current subscribers, so your consumers should tolerate an empty payload instead of trying to parse it as JSON.
A few details matter in practice:
- The topic must match exactly. Retained messages are stored per topic name. Publishing an empty retained message to
sensors/#does nothing, because wildcards are not allowed in topic names you publish to. - The payload must be truly empty. A single space, an empty JSON object
{}or the stringnullare all real payloads and will be retained as the new value. - QoS does not matter much. QoS 0 is fine for a cleanup, but QoS 1 gives you an acknowledgement that the broker received it.
- You need publish permission on that topic. If your credentials cannot publish there, the deletion is rejected or silently dropped depending on the broker and protocol version.
Clear Retained Messages with mosquitto_pub
The Mosquitto command-line tools are the fastest way to clear a single topic. The -r flag sets retain and -n sends a null (zero-length) message:
# Inspect what is currently retained on the topic
mosquitto_sub -h broker.connectmq.com -p 1883 -i inspect-7f3a \
-t "connectmq/demo/7f3a/device42/status" -v -C 1 -W 5
# Delete the retained message
mosquitto_pub -h broker.connectmq.com -p 1883 -i cleanup-7f3a \
-t "connectmq/demo/7f3a/device42/status" -r -n
# Confirm: this should now time out without printing anything
mosquitto_sub -h broker.connectmq.com -p 1883 -i verify-7f3a \
-t "connectmq/demo/7f3a/device42/status" -v -C 1 -W 5
The -C 1 -W 5 combination tells mosquitto_sub to exit after one message or five seconds, which turns it into a quick "is anything retained here?" probe. The examples use the public MQTT test broker with a unique prefix; replace 7f3a with your own random ID.
On a private broker, add credentials and TLS:
mosquitto_pub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
-u YOUR_USERNAME -P YOUR_PASSWORD -i YOUR_CLIENT_ID \
-t "plant1/line2/press/status" -r -n -q 1
Bulk Cleanup with --remove-retained
mosquitto_sub has a helpful option for whole subtrees. With --remove-retained, every retained message it receives is cleared by publishing an empty retained message back to that topic. Combine it with --retained-only so it only touches retained values and ignores live traffic:
mosquitto_sub -h broker.connectmq.com -p 1883 -i purge-7f3a \
-t "connectmq/demo/7f3a/#" --retained-only --remove-retained -v -W 5
Run the inspection command first, read the list carefully, then purge. A wildcard such as # on a shared tree can delete far more than you intended.
Delete a Retained Message with Python
When cleanup is part of a decommissioning script, Python with paho-mqtt 2.x is convenient. Passing payload=None produces a zero-length payload:
import time
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
TOPIC = "connectmq/demo/7f3a/device42/status"
def on_connect(client, userdata, flags, reason_code, properties):
print("Connected:", reason_code)
info = client.publish(TOPIC, payload=None, qos=1, retain=True)
info.wait_for_publish(timeout=5)
print("Retained message cleared on", TOPIC)
client.disconnect()
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="cleanup-py-7f3a")
client.on_connect = on_connect
client.connect(BROKER, 1883, keepalive=30)
client.loop_forever()
Find and Clear a Whole Subtree
This version subscribes with a wildcard, collects every topic that delivers a retained message during a short window, and then clears them one by one. It throttles publishing, which matters on the public broker where each client is limited to 10 messages per second.
import time
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
FILTER = "connectmq/demo/7f3a/#"
found = set()
def on_message(client, userdata, msg):
if msg.retain and msg.payload:
found.add(msg.topic)
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(FILTER, qos=1)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="purge-py-7f3a")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, 1883, keepalive=30)
client.loop_start()
time.sleep(3) # collect retained messages
print(f"Found {len(found)} retained topics")
for topic in sorted(found):
print("Clearing", topic)
client.publish(topic, payload=None, qos=1, retain=True).wait_for_publish(5)
time.sleep(0.15) # stay under the per-client rate limit
client.loop_stop()
client.disconnect()
Note the msg.retain check. Brokers set the retain flag on messages delivered because of a new subscription, so live messages that happen to arrive during the window are not mistaken for stored values.
Delete a Retained Message from JavaScript or the Browser
With MQTT.js the same rule applies: an empty string or empty buffer with retain: true.
const mqtt = require("mqtt");
const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
clientId: "cleanup-js-7f3a",
protocolVersion: 5,
});
client.on("connect", () => {
client.publish(
"connectmq/demo/7f3a/device42/status",
"",
{ qos: 1, retain: true },
(err) => {
console.log(err ? `Failed: ${err.message}` : "Retained message cleared");
client.end();
}
);
});
If you prefer not to write code at all, the in-browser MQTT client in the ConnectMQ portal works too: connect to your broker, enter the exact topic, leave the payload field empty, enable retain and publish. The live topic explorer is a good way to see which topics still hold a value before and after the cleanup.
Troubleshooting a Retained Message That Will Not Go Away
| Symptom | Likely cause | Fix |
|---|---|---|
| Old value reappears seconds later | A device or service is still publishing it with retain | Find the publisher, fix its retain flag, then clear again |
| Nothing changes after the empty publish | Topic mismatch (case, trailing slash, extra level) | Copy the exact topic from a -v subscription output |
| Subscribers crash after cleanup | Consumers parse the empty payload | Skip zero-length messages in the message handler |
| Value is replaced, not removed | Payload was a space, {} or null |
Use -n or payload=None, not a placeholder string |
| Cleanup works on test, not production | Credentials lack publish rights on that topic | Use the account that owns the topic tree |
The first row is by far the most common. A retained value that "comes back" is almost always being republished by something, often a bridge script, a Node-RED flow or a device that publishes its status with retain on every reconnect.
Avoid Stale Retained Data in the First Place
Deleting retained messages is a cleanup task; designing them well reduces how often you need it.
- Use MQTT 5 message expiry. Set a Message Expiry Interval on retained publishes so the broker discards them after a period. The message expiry interval guide covers sensible values.
- Pair status with Last Will. A retained
onlinestatus should be paired with a retainedofflineLast Will message, so the stored value stays accurate when a device drops. - Keep retained topics narrow. Retain state, not streams. High-frequency telemetry rarely needs retain, and fewer retained topics means less to clean.
- Do not treat retained messages as a database. They hold the latest value for convenience, not durable history. Store anything you need to keep in a real database fed by a subscriber.
- Document which topics are retained. A short list in your topic naming conventions prevents surprises when someone adds a wildcard subscription.
The full behaviour of the retain flag is specified in the OASIS MQTT 5.0 standard, and the broader MQTT tutorial puts retained messages in context with QoS, sessions and wills.
Manage Retained Topics on ConnectMQ
ConnectMQ supports retained messages on both MQTT 3.1.1 and 5.0, so every technique above works unchanged. If you are moving beyond experiments, a managed MQTT broker gives you private credentials, TLS on port 8883 and a portal with an in-browser client and live topic explorer for this kind of housekeeping. You can read more about running an MQTT broker in the cloud without operating servers yourself.
Create a free Serverless broker at app.connectmq.com, no credit card required, and you will have a username, password and client ID in a couple of minutes.
Frequently Asked Questions
How do I delete a retained message in MQTT?
Publish a message with a zero-length payload and the retain flag set to the exact same topic. With Mosquitto that is mosquitto_pub -t <topic> -r -n, and the broker stops storing a retained value for that topic.
Can I delete all retained messages with a wildcard?
Not with a single publish, because wildcards are not allowed in topic names. Subscribe with a wildcard filter to discover retained topics, then clear each one, or use mosquitto_sub --retained-only --remove-retained.
Does sending an empty JSON object delete the retained message?
No. {} is a two-byte payload, so the broker retains it as the new value. Only a truly empty payload removes the retained message.
Do subscribers receive the empty message?
Yes, current subscribers receive the zero-length message like any other publish. Make sure your consumers ignore empty payloads instead of failing to parse them.



