MQTT Fundamentals

MQTT Persistent Session and the Clean Session Flag Explained

A persistent session lets a broker remember a client's subscriptions and queue its QoS 1 and 2 messages while it is offline. Learn what is stored, when to use it and the pitfalls to avoid.

MQTT persistent session diagram showing a broker holding subscriptions and queued messages for a disconnected IoT device
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

An MQTT persistent session lets the broker remember who a client is, what it subscribed to and which messages it still owes it, even while the client is disconnected. By the end of this article you will know exactly what the broker stores, how the clean session flag (and its MQTT 5 replacement) controls it, and how to verify the behaviour yourself with a few commands.

What Is an MQTT Persistent Session?

Every MQTT connection has a session: a bundle of state shared by the client and the MQTT broker. With a clean (non-persistent) session, that state lives only as long as the network connection. Disconnect, and the broker throws everything away. With a persistent session, the broker keeps the state after the connection closes and hands it back when the same client reconnects.

The identity that links the old session to the new connection is the client ID. That is why persistent sessions only work when a client reconnects with exactly the same ID every time.

What the Broker Stores

Session state on the broker Stored with persistent session?
The client's subscriptions (topic filters and QoS) Yes
QoS 1 and QoS 2 messages that arrived while the client was offline Yes
QoS 1 and QoS 2 messages sent but not yet acknowledged Yes
QoS 2 messages received from the client but not fully completed Yes
QoS 0 messages published while the client was offline No (brokers may optionally queue them, most do not)
Retained messages Not session state; stored per topic for everyone

The client keeps its own half: QoS 1 and 2 messages it sent but that are not yet acknowledged, and QoS 2 messages it received but not yet completed. A client library that keeps this in memory only will lose its half on reboot, which is a common source of confusion.

What It Does Not Do

A persistent session is not a database. It does not store history, it does not replay messages a client has already acknowledged, and it does not deliver QoS 0 traffic reliably. It also only holds messages for topics the client was already subscribed to before it went offline. If you need the "latest value" for a topic regardless of sessions, use retained messages instead.

The Clean Session Flag in MQTT 3.1.1

In MQTT 3.1.1 a single bit in CONNECT controls everything:

  • cleanSession = true: discard any existing session, start fresh, and discard this session when the client disconnects.
  • cleanSession = false: resume the existing session for this client ID if there is one, otherwise create one, and keep it after disconnect.

The broker tells the client what happened via the Session Present flag in CONNACK. If you connected with cleanSession = false and Session Present is 0, the broker had no state for you, so you must subscribe again. If it is 1, your subscriptions are still in place and queued messages are about to arrive.

The weakness of 3.1.1 is that a persistent session has no built-in expiry. A device that is decommissioned leaves a session behind that keeps collecting messages until the broker's own limits kick in.

Persistent Sessions in MQTT 5

MQTT 5 splits the flag into two independent controls:

  • Clean Start decides whether to discard an existing session at connect time.
  • Session Expiry Interval decides how long the session survives after disconnect, in seconds.

A persistent session in MQTT 5 is therefore Clean Start = false plus a Session Expiry Interval greater than zero. The full mapping, and how to pick values, is covered in MQTT 5 clean start and session expiry interval. Brokers may cap the expiry you request; on the public test broker at broker.connectmq.com the maximum is 30 minutes.

Try an MQTT Persistent Session Step by Step

This experiment uses the public MQTT test broker. Pick your own random suffix instead of p9k2 so you do not collide with other readers, and do not send sensitive data because the public area is readable by everyone.

  1. Start a subscriber with a fixed ID, a persistent session and QoS 1:
mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 \
  -i persist-demo-p9k2 -c -x 1800 -q 1 -v \
  -t 'connectmq/demo/p9k2/alerts'

-c disables clean start and -x 1800 asks for a 30 minute session expiry.

  1. Stop the subscriber with Ctrl+C.

  2. Publish three QoS 1 messages while it is offline:

for i in 1 2 3; do
  mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-demo-p9k2 \
    -q 1 -t 'connectmq/demo/p9k2/alerts' -m "alert $i"
done
  1. Run the exact same subscriber command again. The three messages arrive immediately, because the broker queued them in your session.

  2. Repeat the test without -c. This time nothing arrives on reconnect: the session was cleaned.

The Same Test in Python

import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes

TOPIC = "connectmq/demo/p9k2/alerts"

def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code, "session present:", flags.session_present)
    if not flags.session_present:
        # The broker has no session for us: subscribe again
        client.subscribe(TOPIC, qos=1)

def on_message(client, userdata, msg):
    print(msg.topic, msg.payload.decode())

client = mqtt.Client(
    mqtt.CallbackAPIVersion.VERSION2,
    client_id="persist-py-p9k2",
    protocol=mqtt.MQTTv5,
)
client.on_connect = on_connect
client.on_message = on_message

props = Properties(PacketTypes.CONNECT)
props.SessionExpiryInterval = 1800  # seconds

client.connect("broker.connectmq.com", 1883, keepalive=60,
               clean_start=False, properties=props)
client.loop_forever()

Checking session_present before subscribing is the idiomatic pattern: it avoids re-subscribing needlessly and recovers correctly when the session has expired.

When to Use a Persistent Session

Use one when missing a message matters and the client reconnects with a stable identity:

  • Devices that receive commands (open valve, update config) and may drop off the network briefly.
  • Backend consumers that must process every QoS 1 event, such as alarm handlers.
  • Battery devices that wake, connect, collect pending commands and sleep again.

Prefer a clean session when:

  • The client only cares about live data, like a dashboard that shows current values from retained topics.
  • Client IDs are random or reused across instances, such as autoscaled web workers.
  • You publish only, never subscribe; there is little session state worth keeping.

Pitfalls and Troubleshooting

  1. Random client IDs. Many libraries generate a random ID if you leave it empty. A new ID means a new session every time, so nothing is ever resumed. Set a fixed ID; see MQTT client ID best practices.
  2. Subscribing with QoS 0. The broker only queues messages at the QoS you subscribed with. A QoS 0 subscription gets nothing while offline, even if the publisher used QoS 1.
  3. Publishing with QoS 0. Delivery uses the lower of publish and subscription QoS. QoS 0 publishes are not queued.
  4. Expecting messages from before the first subscribe. A session only queues messages for subscriptions it already has.
  5. Session floods on reconnect. A device offline for hours may receive a burst of queued messages. Size your buffers, or use MQTT 5 message expiry so stale commands die in the queue.
  6. Broker restarts. Session storage is an implementation detail of each broker. On ConnectMQ, sessions are held in memory, so they survive client disconnects but not a broker restart. Design for that: check Session Present and re-subscribe when it is 0, and use retained messages for state that must always be available.
  7. Plan limits. The ConnectMQ Serverless (Free) plan supports QoS up to 1, which is all you need for queued delivery. QoS 2 is available on paid plans.

For a closer look at what gets queued and how much, read how MQTT queues messages for offline clients. New to the protocol overall? The MQTT tutorial starts from the basics.

Run Persistent Sessions on ConnectMQ

ConnectMQ is a hosted MQTT broker with MQTT 3.1.1 and 5.0 support, so both the clean session flag and MQTT 5 session expiry work out of the box. Each broker comes with generated credentials, and client IDs are bound to those credentials, which prevents a stranger from hijacking your device's session. Sign up for the free Serverless plan with no credit card, connect with YOUR_USERNAME, YOUR_PASSWORD and YOUR_CLIENT_ID, and repeat the test above on your own private broker.

Frequently Asked Questions

Does an MQTT persistent session store QoS 0 messages?

Usually not. The specification only requires queuing of QoS 1 and QoS 2 messages; storing QoS 0 is optional, and most brokers skip it. Use QoS 1 for anything you cannot afford to lose.

Why do I not receive queued messages after reconnecting?

The most common causes are a changed client ID, connecting with clean session or clean start set to true, a QoS 0 subscription, or a session that already expired. Check the Session Present flag in CONNACK to see whether the session was resumed.

Do I need to subscribe again with a persistent session?

Only if Session Present is 0. When the broker resumes your session, your subscriptions already exist and you can skip subscribing.

Do persistent sessions survive a broker restart?

It depends on the broker. ConnectMQ keeps sessions in memory, so they survive client disconnects but are cleared if the broker restarts; clients should handle Session Present = 0 gracefully.

Related articles

Why Your MQTT Client Keeps Disconnecting (and How to Fix It)

Reconnect loops are one of the most common MQTT problems. Learn the five usual causes, from duplicate client IDs to keep alive timeouts and oversized packets, and follow a diagnosis flow that pinpoints yours.

8 min read

Who Invented MQTT? The History from Oil Pipelines to IoT

MQTT was invented in 1999 by Andy Stanford-Clark of IBM and Arlen Nipper of Arcom to monitor oil pipelines over satellite. Follow its path to open source, OASIS, ISO and today's managed cloud brokers.

7 min read

MQTT Maximum Message Size: Limits and Large Payload Strategies

MQTT can technically carry a 256 MB packet, but brokers, client libraries and devices impose far lower limits. Learn where the real limits are, what happens when you exceed them, and how to send large payloads.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.