If your MQTT client keeps disconnecting, the cause is almost always one of five things: two clients sharing a client ID, a keep alive timeout, the network silently dropping idle connections, a packet the broker will not accept, or a plan or rate limit. This guide explains how to recognise each one from the symptoms and logs, and walks through a diagnosis flow that gets you to the fix quickly.
Start with the Disconnect Pattern
Before changing any settings, observe how the disconnects happen. The timing tells you a lot:
| Pattern | Most likely cause |
|---|---|
| Two devices take turns disconnecting every few seconds | Duplicate client ID |
| Disconnect at a regular interval, roughly 1.5 x keep alive | Keep alive not being serviced |
| Disconnect after several minutes of idle time, only on certain networks | NAT or firewall idle timeout |
| Disconnect right after publishing a particular message | Packet too large or invalid topic |
| Disconnect when traffic spikes | Rate limit or quota |
| Disconnect immediately after every connect | Authorization or subscription error |
Also find out which side closes the connection. With MQTT 5, a broker-initiated close comes with a DISCONNECT packet and a reason code. With MQTT 3.1.1, the broker just closes the socket. If you can, switch the client to MQTT 5 while debugging; the reason code often answers the question instantly. The MQTT reason codes reference lists them all.
Cause 1: Duplicate Client IDs
The MQTT specification says that if a client connects with a client ID that is already connected, the broker must disconnect the existing client. If both clients have automatic reconnect enabled, they kick each other off forever. In MQTT 5 the losing client receives reason code 0x8E Session taken over.
This happens more often than you would think:
- Firmware with a hard-coded client ID flashed onto several devices.
- A service scaled to two replicas with the same configuration.
- A developer running the same script in two terminals.
- A desktop MQTT tool left open with the ID your device uses.
Fix: derive client IDs from something unique, such as a MAC address, serial number or hostname, and never share an ID between processes. The MQTT client ID guide covers naming patterns. On managed brokers that issue client IDs with credentials, run one connection per issued ID.
Cause 2: Keep Alive Timeouts
Keep alive is a promise from the client: "you will hear from me at least every N seconds". If the broker receives nothing for 1.5 times that interval, it closes the connection, with reason 0x8D Keep Alive timeout in MQTT 5. The client sends PINGREQ when it has nothing else to send, but only if its network loop is running.
Common ways to break it:
- Calling a long blocking function, such as
time.sleep(120)or a slow HTTP request, in the same thread that runs the MQTT loop. - On Arduino, forgetting to call
mqtt.loop()frequently, or usingdelay()for minutes at a time. - Setting keep alive very short, such as 5 seconds, on a high-latency cellular link.
- Deep-sleep cycles longer than the keep alive interval without a clean disconnect.
Fix: run the network loop in a background thread (loop_start() in Paho, which MQTT.js does automatically), keep blocking work off that thread, and choose a keep alive of 30 to 120 seconds for most deployments. The keep alive interval guide explains how to tune it.
Cause 3: NAT and Firewall Idle Timeouts
Mobile networks, home routers and corporate firewalls track TCP connections and silently drop them after a period of inactivity. Neither the client nor the broker is told. The client discovers the problem only when it next tries to send, and the broker only when keep alive expires.
The tell-tale sign is that disconnects happen on cellular or one specific site, but not on your development network. Fix: set keep alive lower than the network's idle timeout, so PINGREQ traffic keeps the mapping alive. If a carrier drops idle connections after a few minutes, a 60 second keep alive is a reasonable starting point.
Cause 4: Oversized or Invalid Packets
Brokers close the connection when they receive a packet they cannot accept. Typical triggers:
- Payload above the broker's maximum message size. MQTT 5 clients see
0x95 Packet too large; 3.1.1 clients just lose the connection. See the MQTT message size limit guide. - Publishing to a topic containing
+or#. Wildcards are only valid in subscriptions. - Invalid UTF-8 in a topic name produced by string concatenation with binary data.
- Topic alias misuse after a reconnect in MQTT 5.
Fix: log the topic and payload size before each publish, and check whether the disconnect follows a specific message. The same oversized message is often retried after reconnect, which creates a loop.
Cause 5: Rate Limits and Quotas
Shared and managed brokers protect themselves with limits. Publishing faster than allowed, opening more connections than your plan permits, or exceeding a subscription count can cause refusals or disconnects. In MQTT 5, look for 0x96 Message rate too high and 0x97 Quota exceeded.
On the public MQTT test broker at broker.connectmq.com, each client is limited to 10 messages per second, 10 subscriptions and 64 KB per message. Private brokers have per-plan limits instead; the free Serverless plan, for example, allows up to 25 connections and 256 KB messages.
A Diagnosis Flow That Works
- Enable client logging. In Paho,
client.enable_logger(); in Mosquitto tools,-d; in MQTT.js, setDEBUG=mqttjs*. - Connect with MQTT 5 and log the reason code and Reason String from every DISCONNECT.
- Record the timestamps of connects and disconnects and compare the interval with your keep alive.
- Check the broker side. Look at the connection state of the client ID on the broker and whether another connection with the same ID appears at the same moment.
- Reproduce with a minimal client. Run
mosquitto_subwith the same credentials on the same network. If it stays connected, the problem is in your application. - Change one thing at a time: client ID, keep alive, payload size, publish rate.
Here is a minimal Python client that logs everything you need for steps 1 to 3:
import time
import paho.mqtt.client as mqtt
started = time.time()
def ts():
return f"[{time.time() - started:7.1f}s]"
def on_connect(client, userdata, flags, reason_code, properties):
print(ts(), "CONNECT", reason_code, "session_present:", flags.session_present)
client.subscribe("connectmq/demo/5a3c/#", qos=1)
def on_disconnect(client, userdata, flags, reason_code, properties):
reason = getattr(properties, "ReasonString", "") if properties else ""
print(ts(), "DISCONNECT", reason_code, f"(0x{reason_code.value:02X})", reason)
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id="disconnect-debug-5a3c",
protocol=mqtt.MQTTv5,
)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.reconnect_delay_set(min_delay=1, max_delay=60)
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_forever(retry_first_connection=True)
To watch the broker's view of a client from another terminal, subscribe to its connection state topic:
mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
-u YOUR_USERNAME -P YOUR_PASSWORD -i YOUR_MONITOR_CLIENT_ID \
-t '$SYS/broker/connection/YOUR_CLIENT_ID/state' -v
Quote the topic in single quotes so the shell does not expand $SYS. The MQTT $SYS topics guide explains what else you can monitor this way. Because the broker binds client IDs to credentials, the monitoring connection needs its own valid client ID; the in-browser MQTT client in the portal is an easy alternative for a quick look.
Reconnect Logic That Does Not Make Things Worse
Automatic reconnection is essential, but a careless implementation turns one problem into many:
- Use exponential backoff with jitter so a fleet does not reconnect in lockstep after an outage.
- Do not retry on authorization errors. Codes
0x86and0x87will not fix themselves, and repeated failed logins can get your IP blocked. - Re-subscribe in
on_connect, not once at startup, unless you rely on a persistent session. - Use a Last Will so other systems know when a device drops unexpectedly. See MQTT Last Will and Testament.
Diagnose Disconnects with ConnectMQ
ConnectMQ gives you broker-side visibility that self-hosted setups often lack. The portal dashboard and devices view show which clients are connected, the live topic explorer shows what they publish, and the $SYS/broker/connection/{clientId}/state topic lets your own tools track connection changes. MQTT 5 clients receive reason codes for quota and protocol violations, so you can see why a disconnect happened instead of guessing. For the background on what an MQTT broker does during a connection's lifetime, see our explainer, or the MQTT tutorial for the full protocol.
Get Stable Connections with ConnectMQ
A fully managed MQTT service takes broker uptime off your list so you can focus on client behaviour. Create a free Serverless broker at app.connectmq.com, no credit card required, and test your reconnect logic against real limits before you ship. Compare options in our guide to the cloud MQTT broker landscape.
Frequently Asked Questions
Why does my MQTT client disconnect every few seconds?
The most common cause is another client using the same client ID. Each connection kicks the other off, and both reconnect automatically. Give every client a unique ID.
What keep alive value should I use?
Between 30 and 120 seconds suits most deployments. Use a value below your network's idle timeout, and make sure the client's network loop runs continuously so PINGREQ is actually sent.
How do I know whether the broker or the client closed the connection?
Connect with MQTT 5. If the broker closed it, the client receives a DISCONNECT packet with a reason code; if the socket simply dropped, the client reports a network error without one.
Can a large message cause disconnects?
Yes. If a packet exceeds the broker's maximum size, the broker closes the connection, and if the client retries the same message after reconnecting, it disconnects again. Check payload sizes against your broker's limit.



