Paho MQTT Python is the standard way to talk MQTT from Python, whether you are scripting a Raspberry Pi sensor, building a backend service or testing a device. In this tutorial you will install paho-mqtt 2.x, write a subscriber and a publisher that talk through broker.connectmq.com, and understand the network loop well enough to avoid the usual pitfalls. At the end you will switch the same code to a private broker with credentials and TLS.
What You Need
- Python 3.8 or newer (
python --version) - A terminal with outbound access to port 1883, or 8883 for TLS
- Ten minutes
No account is needed for the first part. The free public MQTT broker at broker.connectmq.com accepts anonymous connections with any non-empty, unique client ID. Its public area is readable by anyone, so use a random topic prefix and never send real data there.
Install paho-mqtt
Create a virtual environment and install the library:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install "paho-mqtt>=2.0"
python -c "import paho.mqtt; print(paho.mqtt.__version__)"
You should see a 2.x version. Version 2.0 changed how the client is constructed and how callbacks are called. Many tutorials online still show the 1.x style, which now fails or prints deprecation warnings. Everything here uses the current style; if you are migrating existing code, see the paho-mqtt 2.0 callback API version guide.
Step 1: A Subscriber
Save this as subscriber.py. Change k7f3q2 to your own random string:
import uuid
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
PORT = 1883
TOPIC = "connectmq/demo/k7f3q2/#"
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"Connection failed: {reason_code}")
return
print(f"Connected to {BROKER}")
# Subscribe here so the subscription is restored after every reconnect.
client.subscribe(TOPIC, qos=1)
def on_subscribe(client, userdata, mid, reason_code_list, properties):
for rc in reason_code_list:
if rc.is_failure:
print(f"Subscription rejected: {rc}")
else:
print(f"Subscribed with QoS {rc.value}")
def on_message(client, userdata, msg):
retained = " (retained)" if msg.retain else ""
print(f"{msg.topic}{retained}: {msg.payload.decode('utf-8', errors='replace')}")
def on_disconnect(client, userdata, disconnect_flags, reason_code, properties):
print(f"Disconnected: {reason_code}")
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"sub-{uuid.uuid4().hex[:8]}")
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.on_disconnect = on_disconnect
client.connect(BROKER, PORT, keepalive=60)
client.loop_forever()
Run it with python subscriber.py. It prints "Connected" and "Subscribed with QoS 1", then waits.
Step 2: A Publisher
In a second terminal, save this as publisher.py:
import json
import random
import time
import uuid
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
PORT = 1883
PREFIX = "connectmq/demo/k7f3q2"
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"pub-{uuid.uuid4().hex[:8]}")
# Last Will: published by the broker if this client disappears without disconnecting.
client.will_set(f"{PREFIX}/status/publisher", payload="offline", qos=1, retain=True)
client.connect(BROKER, PORT, keepalive=60)
client.loop_start() # network loop in a background thread
# Retained status: new subscribers get it immediately.
client.publish(f"{PREFIX}/status/publisher", "online", qos=1, retain=True)
try:
for i in range(10):
payload = json.dumps({"seq": i, "temperature": round(random.uniform(20, 25), 2)})
info = client.publish(f"{PREFIX}/sensors/room1", payload, qos=1)
info.wait_for_publish(timeout=5)
print(f"Sent #{i} (mid={info.mid}, published={info.is_published()})")
time.sleep(1)
finally:
client.publish(f"{PREFIX}/status/publisher", "offline", qos=1, retain=True).wait_for_publish(5)
client.disconnect()
client.loop_stop()
Run python publisher.py. The subscriber prints the status message and ten sensor readings, one per second.
Understanding the Paho MQTT Python Network Loop
The network loop is the part of paho that reads from and writes to the socket, sends keepalive pings and calls your callbacks. Nothing happens without it. You have three ways to run it:
| Method | Blocks? | Use it when |
|---|---|---|
loop_forever() |
Yes, until disconnect() |
The script only reacts to messages, such as a subscriber or logger |
loop_start() / loop_stop() |
No, runs in a background thread | Your main thread does other work, such as reading sensors |
loop(timeout) |
One iteration per call | You integrate with your own event loop |
The most common beginner bug is calling publish() without any loop running and wondering why nothing arrives, or why QoS 1 messages are never acknowledged. Another is forgetting that callbacks run on the loop thread when you use loop_start(); if a callback touches data your main thread also uses, protect it with a lock or a queue.Queue.
loop_forever() and loop_start() both reconnect automatically after a dropped connection. That is why the subscriber subscribes inside on_connect: with a clean session, subscriptions are lost on reconnect and must be made again. The paho-mqtt reconnect guide goes deeper into backoff and offline buffering.
QoS, Retained Messages and Last Will in Practice
The publisher used three MQTT features worth understanding:
QoS 1 means the broker acknowledges each message, and paho resends it if the acknowledgment does not arrive. wait_for_publish() blocks until that acknowledgment, which is handy in scripts that exit right after publishing. QoS 0 is fire-and-forget and slightly cheaper; QoS 2 guarantees exactly-once delivery at the cost of an extra round trip. Our MQTT QoS levels explained article compares them in detail.
Retained messages are stored by the broker, one per topic, and sent immediately to anyone who subscribes later. They are ideal for state such as "online" or a device's current configuration. Restart the subscriber and notice that it receives the retained status straight away, marked "(retained)".
Last Will is registered at connect time. If the publisher crashes or loses its network, the broker publishes "offline" for it after the keepalive expires. Press Ctrl+C during the loop and you will see the clean "offline" from the finally block; kill the process with kill -9 instead and the broker sends the will.
Testing With Command-Line Tools
You can verify your Python code from the other side with the Mosquitto clients:
mosquitto_sub -h broker.connectmq.com -p 1883 -i cli-k7f3q2 -t 'connectmq/demo/k7f3q2/#' -v
The ConnectMQ portal offers the same view without installing anything: its in-browser MQTT client and live topic explorer show every topic under your prefix as messages arrive, which is useful when you are debugging on a machine where you cannot install Mosquitto. Keep in mind that the portal works with your private broker, while the CLI command above targets the public one.
If the CLI receives your messages but your Python subscriber does not, the problem is in your subscriber code; if neither receives them, look at the publisher or the network.
Common Errors and How to Fix Them
ValueError: Unsupported callback API versionor aTypeErroraboutcallback_api_version: you are using 1.x-style construction with paho-mqtt 2.x. Passmqtt.CallbackAPIVersion.VERSION2as the first argument.TypeError: on_connect() takes 4 positional arguments but 5 were given: your callback has the old signature. Use the version 2 signatures shown above.ConnectionRefusedErroror timeouts: port 1883 is blocked on your network. Try TLS on 8883.- Connected, then disconnected in a loop: another client is using the same client ID. Generate a unique one, as the examples do.
- Nothing received: check that topics match exactly, including case and the prefix, and that a loop is running.
- Messages dropped on the public broker: it allows 10 messages per second per client and 64 KB per message. Slow down or batch readings.
Moving to a Private Broker With Credentials and TLS
For real data, create your own broker. Sign up at app.connectmq.com, choose the free Serverless plan, create a broker, and copy the generated username, password and client ID. Then change the connection code:
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.tls_set() # uses the system CA store and verifies the hostname
client.connect("broker.connectmq.com", 8883, keepalive=60)
Use the host name shown for your broker in the portal. ConnectMQ brokers use a Let's Encrypt certificate, which the default CA bundle on most systems already trusts. If you hit certificate errors, the Python MQTT TLS guide walks through each one. Keep credentials in environment variables rather than in source files.
Where to Go Next
Once publish and subscribe work, these guides build on the same client:
- Structure payloads properly with the Python MQTT JSON tutorial.
- Run many concurrent tasks with aiomqtt and asyncio.
- Persist messages with the MQTT to SQLite in Python tutorial.
- Compare Python with other languages in the MQTT client libraries overview.
The official Eclipse Paho Python documentation is the reference for every method and option.
Get Started with ConnectMQ
ConnectMQ is a managed MQTT broker that supports MQTT 3.1.1 and 5.0 on TCP, TLS and WebSocket, with a portal that includes an in-browser MQTT client and a live topic explorer for checking what your Python code publishes. Experiment on the public broker, then run production traffic on your own cloud MQTT broker. Create a free account on the Serverless plan, no credit card required.
Frequently Asked Questions
Which version of paho-mqtt should I use?
Use the current 2.x release. It requires a callback_api_version argument when creating the client, and version 2 callbacks are the recommended choice for new code.
What is the difference between loop_start and loop_forever?
loop_forever() blocks the calling thread and runs the network loop until you disconnect. loop_start() runs the same loop in a background thread so your main code keeps running.
Does paho mqtt python support MQTT 5?
Yes. Pass protocol=mqtt.MQTTv5 when creating the client to use MQTT 5 features such as properties, reason codes and session expiry.
Do I need an account to try paho-mqtt with ConnectMQ?
No. broker.connectmq.com accepts anonymous clients on port 1883 for testing. Create a free account when you need a private broker with credentials.



