Sitemap
Kethan

Kethan

How to Seamlessly Connect to an MQTT Broker

3 min readJul 4, 2026

--

Why this matters

MQTT is great for sending small messages between devices and systems. But in real life, connections can still break due to internet issues, an incorrect security setup, or weak retry handling. This guide explains, in simple terms, how to keep your MQTT connection stable using Python.

Version notes (important)

  • MQTT has two common versions: 3.1.1 and 5.0.
  • This article uses 3.1.1 because it works with many existing brokers.
  • The Python library used is `paho-mqtt`.
  • The same stability ideas in this blog also apply to MQTT 5.0.

What “seamless” means

A seamless MQTT connection should:

  • Connect safely.
  • Reconnect by itself if the network drops.
  • Continue receiving messages after reconnect.
  • Keep running without manual restarts.
  • Reduce message loss as much as possible.

Prerequisites

  • An MQTT broker endpoint (host + port).
  • Credentials (username/password) or client certificate files
  • Python package:
pip install paho-mqtt

Step 1: Choose the right connection settings

Use these beginner-friendly configurations:

  • keepalive=60: checks every minute that the connection is still alive.
  • clean_session=False: helps the client continue where it left off.
  • QoS 1 for important data: the message is delivered at least once.
  • Use TLS on port 8883(default): this encrypts traffic.

Step 2: Implement a resilient MQTT client (Python)

The code below is designed to be practical and easy to follow.

import ssl
import time
import random
from paho.mqtt import client as mqtt_client

BROKER = "mqtt.example.com"
PORT = 8883
CLIENT_ID = "plant-gateway-001"
USERNAME = "your-username"
PASSWORD = "your-password"
TOPICS = [("factory/line1/alerts", 1), ("factory/line1/status", 1)]


def build_client() -> mqtt_client.Client:
client = mqtt_client.Client(
client_id=CLIENT_ID,
clean_session=False, # persistent session for MQTT 3.1.1
protocol=mqtt_client.MQTTv311,
)

# Authentication
client.username_pw_set(USERNAME, PASSWORD)

# TLS (recommended for production)
client.tls_set(
cert_reqs=ssl.CERT_REQUIRED,
tls_version=ssl.PROTOCOL_TLS_CLIENT,
)
client.tls_insecure_set(False)

# Automatic reconnect with controlled backoff
client.reconnect_delay_set(min_delay=1, max_delay=30)

def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Connected to broker")
# Subscribe again after connect to make sure topics are active.
client.subscribe(TOPICS)
else:
print(f"Connect failed with code: {rc}")

def on_disconnect(client, userdata, rc):
print(f"Disconnected (rc={rc})")

def on_log(client, userdata, level, buf):
# Useful debug logs when something goes wrong.
print(f"LOG[{level}] {buf}")

def on_message(client, userdata, msg):
print(f"{msg.topic}: {msg.payload.decode(errors='replace')}")

client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_log = on_log
client.on_message = on_message

return client


def run_forever() -> None:
client = build_client()

while True:
try:
client.connect(BROKER, PORT, keepalive=60)
client.loop_forever(retry_first_connection=True)
except Exception as exc:
# Safety retry if the loop exits unexpectedly.
wait_seconds = min(30, 2 + random.randint(0, 4))
print(f"Connection error: {exc}. Retrying in {wait_seconds}s")
time.sleep(wait_seconds)


if __name__ == "__main__":
run_forever()

Why `on_log` adds real value

`on_log` is not mandatory, but it is extremely useful for real deployments.

  1. Speeds up root-cause analysis when connections flap.

2. Shows protocol-level hints (connect/disconnect attempts, retries, handshake details).

3. Helps correlate app-side behaviour with broker-side logs.

4. Reduces mean time to recovery during outages because you can see exactly where the flow fails.

Recommended practice:

- Keep detailed `on_log` enabled in dev/stage.

Get Kethan’s stories in your inbox

Join Medium for free to get updates from this writer.

- In production, route logs to your logging system and throttle/filter verbosity to avoid noise.

Step 3: Publish safely

For critical messages:

  • Send with `qos=1`.
  • Wait for the publish confirmation.
  • If data is very important, keep a local backup queue during outages.

Example:

result = client.publish("factory/line1/heartbeat", payload="alive", qos=1, retain=False)
result.wait_for_publish()

Step 4: Design topics for scalability

Use a consistent hierarchy:

factory/{site}/{line}/{device}/telemetry
factory/{site}/{line}/{device}/alerts
factory/{site}/{line}/{device}/command

Tips:

  • Use one naming style everywhere.
  • Keep payload format versioned (example: ” schema”: “1.0”`).
  • Keep device data topics separate from command topics.

Step 5: Add operational guardrails

To keep connectivity truly seamless in production:

  • Set Last Will and Testament (LWT) so others know when a client goes offline.
  • Track simple health numbers: reconnect count and last message time.
  • Rotate passwords/certificates safely.
  • Alert when reconnect happens too often.

Common pitfalls to avoid

  • Using MQTT without TLS in production.
  • Trusting default reconnect settings only.
  • Not subscribing again after reconnect.
  • Using QoS 0 for important data.
  • Not handling duplicate messages on the receiving side.

MQTT error codes and references

If `on_connect` returns a non-zero `rc`, it means the broker rejected the connection.

Use these references to understand the exact reason code.

Tip: always log the code number and a short human-friendly message.

Final takeaways

A stable MQTT setup is not just one `connect()` call.

It is about handling disconnects and retries the right way.

If you use TLS, good reconnect logic, correct QoS, and clear topic naming, your client will stay reliable even when the network is unstable.

--

--