An MQTT session isn’t just a connection; it’s a stateful agreement between a client and a broker that persists even after the physical network link drops.

Let’s see this in action. Imagine a simple IoT device publishing sensor readings.

import paho.mqtt.client as mqtt

# On connect callback
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("Connected successfully with result code "+str(rc))
    else:
        print("Connection failed with result code "+str(rc))

# On message callback
def on_message(client, userdata, msg):
    print(f"Received message: {msg.payload.decode()} on topic {msg.topic}")

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message

# Connect with a clean session (session_present flag will be 0)
client.connect("mqtt.eclipseprojects.io", 1883, 60)
client.loop_start()

# Publish a message
client.publish("my/test/topic", "Hello, MQTT!")

# Let it run for a bit
import time
time.sleep(5)

client.loop_stop()
client.disconnect()

In this snippet, client = mqtt.Client() by default creates a clean session. When this client connects, the broker essentially wipes the slate clean regarding any previous session state for this client ID. If the client disconnects and reconnects, it starts fresh.

Now, consider persistent sessions. We enable this by setting client.clean_session = False before connecting and by ensuring the client ID is consistent across reconnections.

import paho.mqtt.client as mqtt

# ... (on_connect and on_message callbacks as above)

client = mqtt.Client("my_persistent_client_id") # Consistent client ID
client.on_connect = on_connect
client.on_message = on_message
client.clean_session = False # Crucial for persistent sessions

# Connect with a persistent session (session_present flag will be 1 if resumed)
client.connect("mqtt.eclipseprojects.io", 1883, 60)
client.loop_start()

# Subscribe to a topic with QoS 1
client.subscribe("my/persistent/topic", qos=1)

# Publish a message
client.publish("my/persistent/topic", "Persistent message", qos=1)

# Simulate a network interruption
print("Simulating network interruption...")
client.loop_stop() # Stop the network loop
time.sleep(10) # Keep the client object alive but disconnected

print("Reconnecting...")
client.loop_start() # Restart the network loop
client.connect("mqtt.eclipseprojects.io", 1883, 60) # Reconnect

# After reconnecting, if the session was persistent,
# the broker would have held onto messages published with QoS 1 or 2
# while the client was offline and delivered them upon reconnection.
# You might also receive messages that were published *while* the client was offline,
# depending on the broker's configuration and the QoS level.

time.sleep(5)
client.loop_stop()
client.disconnect()

The core problem MQTT sessions solve is ensuring message delivery and state synchronization in unreliable network environments, common in IoT. A clean session (default, clean_session=True) means the broker discards all session information (subscriptions, pending QoS 1/2 messages) when the client disconnects. A persistent session (clean_session=False) tells the broker to keep this information. If the client reconnects with the same client ID, the broker restores the session, including any undelivered messages for that client and its subscriptions.

The session_present flag in the on_connect callback is key. When a client connects with clean_session=False, if the broker finds an existing session for that client ID, session_present will be True (or 1 in rc flags). If it’s a new session or the old one was cleaned up, session_present will be False (or 0). This allows the client to know if it’s resuming a previous state or starting anew.

The real magic of persistent sessions lies in Guaranteed Delivery. For messages published with QoS 1 or 2, if a client with a persistent session disconnects before acknowledging receipt, the broker will hold onto those messages. Upon reconnection, the broker will attempt to redeliver them. Similarly, if a client subscribes to a topic with QoS 1 or 2 and disconnects, the broker will queue messages published to that topic while the client was offline, delivering them when the client reconnects and the session is resumed.

What most people overlook is that a persistent session is tied to the client ID. If two clients connect with the same clean_session=False and the same client ID, only the last one to connect will have its session actively maintained by the broker. The previous client, upon reconnecting with that same ID, will effectively start a new session, and the broker will discard the old one. This means careful management of client IDs is paramount for reliable state management.

The next hurdle is understanding how brokers handle message queuing and expiration for persistent sessions, especially concerning message expiry intervals.

Want structured learning?

Take the full Mqtt course →