MQTT reason codes are the single-byte status values that MQTT 5 brokers and clients put into acknowledgements and DISCONNECT packets to say exactly what happened. This reference lists every code, which packets can carry it, what usually triggers it and how to fix it, so you can turn a cryptic 0x87 or 0x97 in your logs into a concrete next step.
How MQTT Reason Codes Work
MQTT 3.1.1 had only a handful of status values: six CONNACK return codes and a single failure value (0x80) in SUBACK. Everything else, such as a rejected publish or a broker-side disconnect, was silent; the connection just closed. MQTT 5 replaced this with a shared set of reason codes used across CONNACK, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, UNSUBACK, DISCONNECT and AUTH. It is one of the most practical MQTT 5 features for anyone operating devices in the field.
Two rules make the table easy to read:
- Values below
0x80mean success or a non-error outcome, such as "granted QoS 1" or "no matching subscribers". - Values of
0x80and above mean failure. The packet that carries the code tells you which operation failed.
Many packets can also carry a Reason String property, a human-readable explanation from the sender. Always log it when present; it often contains the exact limit that was hit.
Success and Informational Codes
| Code | Name | Packets | What it means |
|---|---|---|---|
| 0x00 | Success / Normal disconnection / Granted QoS 0 | CONNACK, PUBACK, PUBREC, PUBREL, PUBCOMP, UNSUBACK, AUTH, DISCONNECT, SUBACK | Operation succeeded; in SUBACK it means the subscription was granted at QoS 0 |
| 0x01 | Granted QoS 1 | SUBACK | Subscription accepted with maximum QoS 1 |
| 0x02 | Granted QoS 2 | SUBACK | Subscription accepted with maximum QoS 2 |
| 0x04 | Disconnect with Will Message | DISCONNECT | Client is leaving cleanly but asks the broker to publish its Will |
| 0x10 | No matching subscribers | PUBACK, PUBREC | Publish accepted, but nobody was subscribed |
| 0x11 | No subscription existed | UNSUBACK | You unsubscribed from a filter you were not subscribed to |
| 0x18 | Continue authentication | AUTH | Enhanced authentication exchange continues |
| 0x19 | Re-authenticate | AUTH | Client starts re-authentication on an existing connection |
A SUBACK granting a lower QoS than you requested is not an error. If you ask for QoS 2 and get 0x01, the broker's maximum QoS is 1 for your connection, and messages will arrive at QoS 1 at most.
Error Reason Codes Reference
| Code | Name | Packets | Usual cause | Fix |
|---|---|---|---|---|
| 0x80 | Unspecified error | All acknowledgements, DISCONNECT | Broker declined without a specific reason | Read the Reason String and broker logs |
| 0x81 | Malformed Packet | CONNACK, DISCONNECT | Invalid encoding, bad UTF-8, wrong lengths | Update the client library; check custom encoders |
| 0x82 | Protocol Error | CONNACK, DISCONNECT | Packet valid but sent in the wrong order or with forbidden fields | Check for duplicate CONNECT or misuse of properties |
| 0x83 | Implementation specific error | CONNACK, PUBACK, PUBREC, SUBACK, UNSUBACK, DISCONNECT | Broker-specific policy rejected the operation | Check broker documentation and Reason String |
| 0x84 | Unsupported Protocol Version | CONNACK | Broker does not speak the requested version | Use MQTT 3.1.1 or 5.0 as supported |
| 0x85 | Client Identifier not valid | CONNACK | Empty, too long or disallowed client ID | Use a valid, unique client ID |
| 0x86 | Bad User Name or Password | CONNACK | Wrong or malformed credentials | Re-copy username and password |
| 0x87 | Not authorized | CONNACK, PUBACK, PUBREC, SUBACK, UNSUBACK, DISCONNECT | Credentials or client lack permission | Check credentials, client ID binding and topic rights |
| 0x88 | Server unavailable | CONNACK | Broker cannot accept connections right now | Retry with backoff |
| 0x89 | Server busy | CONNACK, DISCONNECT | Broker is overloaded | Retry later with backoff and jitter |
| 0x8A | Banned | CONNACK | Client or address is blocked | Stop retry loops; fix credentials; contact the operator |
| 0x8B | Server shutting down | DISCONNECT | Planned shutdown or restart | Reconnect with backoff |
| 0x8C | Bad authentication method | CONNACK, DISCONNECT | Enhanced auth method not supported | Remove or correct the Authentication Method |
| 0x8D | Keep Alive timeout | DISCONNECT | No packet received within 1.5 x keep alive | Service the network loop; check connectivity |
| 0x8E | Session taken over | DISCONNECT | Another client connected with the same client ID | Give every client a unique ID |
| 0x8F | Topic Filter invalid | SUBACK, UNSUBACK, DISCONNECT | Malformed filter, e.g. a/#/b |
Fix wildcard placement |
| 0x90 | Topic Name invalid | CONNACK, PUBACK, PUBREC, DISCONNECT | Wildcards or invalid characters in a publish topic | Publish to a concrete topic name |
| 0x91 | Packet Identifier in use | PUBACK, PUBREC, SUBACK, UNSUBACK | Packet ID reused while still in flight | Library bug or custom packet ID handling |
| 0x92 | Packet Identifier not found | PUBREL, PUBCOMP | QoS 2 state lost, often after a session reset | Check session persistence settings |
| 0x93 | Receive Maximum exceeded | DISCONNECT | Too many unacknowledged QoS 1/2 publishes | Respect the peer's Receive Maximum |
| 0x94 | Topic Alias invalid | DISCONNECT | Alias out of range or undefined | Reset aliases on reconnect; honour the maximum |
| 0x95 | Packet too large | CONNACK, DISCONNECT | Packet exceeds the Maximum Packet Size | Shrink or chunk payloads |
| 0x96 | Message rate too high | DISCONNECT | Publishing faster than allowed | Throttle publishes |
| 0x97 | Quota exceeded | CONNACK, PUBACK, PUBREC, SUBACK, DISCONNECT | A plan or account limit was reached | Reduce usage or upgrade the plan |
| 0x98 | Administrative action | DISCONNECT | An operator disconnected the client | Check with the broker administrator |
| 0x99 | Payload format invalid | CONNACK, PUBACK, PUBREC, DISCONNECT | Payload Format Indicator says UTF-8 but bytes are not | Fix encoding or clear the indicator |
| 0x9A | Retain not supported | CONNACK, DISCONNECT | Retain flag used where not allowed | Publish without retain |
| 0x9B | QoS not supported | CONNACK, DISCONNECT | Publish QoS above the broker's maximum | Lower the QoS to the advertised maximum |
| 0x9C | Use another server | CONNACK, DISCONNECT | Broker redirects the client temporarily | Follow the Server Reference property |
| 0x9D | Server moved | CONNACK, DISCONNECT | Broker redirects permanently | Update the configured host |
| 0x9E | Shared Subscriptions not supported | SUBACK, DISCONNECT | $share/... filter not allowed |
Use a plan or broker with shared subscriptions |
| 0x9F | Connection rate exceeded | CONNACK | Too many connection attempts | Add backoff to reconnect logic |
| 0xA0 | Maximum connect time | DISCONNECT | Connection lifetime limit reached | Reconnect; expect periodic disconnects |
| 0xA1 | Subscription Identifiers not supported | SUBACK, DISCONNECT | Subscription Identifier property not allowed | Remove the property |
| 0xA2 | Wildcard Subscriptions not supported | SUBACK, DISCONNECT | Wildcards not allowed for this client | Subscribe to explicit topics |
Note that some codes, such as 0x8D and 0x8E, appear only in a DISCONNECT sent by the broker. Others, like 0x84 to 0x88, can only appear in CONNACK because they describe why the connection was refused in the first place.
Mapping MQTT 3.1.1 Return Codes to MQTT 5
If you support both protocol versions, this mapping keeps your error handling consistent. The detailed walkthrough is in the guide to MQTT CONNACK return codes.
| MQTT 3.1.1 CONNACK | MQTT 5 equivalent |
|---|---|
| 0 Connection accepted | 0x00 Success |
| 1 Unacceptable protocol version | 0x84 Unsupported Protocol Version |
| 2 Identifier rejected | 0x85 Client Identifier not valid |
| 3 Server unavailable | 0x88 Server unavailable |
| 4 Bad user name or password | 0x86 Bad User Name or Password |
| 5 Not authorized | 0x87 Not authorized |
Logging MQTT Reason Codes in Python
With paho-mqtt 2.x and CallbackAPIVersion.VERSION2, every callback receives a ReasonCode object. Its string form is the spec name and .value is the numeric code, which is exactly what you want in logs:
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, reason_code, properties):
print(f"CONNACK: {reason_code} (0x{reason_code.value:02X})")
if reason_code.is_failure:
return
client.subscribe("connectmq/demo/b7d2/#", qos=2)
def on_subscribe(client, userdata, mid, reason_code_list, properties):
for rc in reason_code_list:
print(f"SUBACK: {rc} (0x{rc.value:02X})")
def on_disconnect(client, userdata, flags, reason_code, properties):
reason = getattr(properties, "ReasonString", None) if properties else None
print(f"DISCONNECT: {reason_code} (0x{reason_code.value:02X}) {reason or ''}")
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id="reason-demo-b7d2",
protocol=mqtt.MQTTv5,
)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_disconnect = on_disconnect
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_forever()
Run this against the public MQTT test broker and you will see Success on connect and a granted QoS in SUBACK. Try subscribing to an invalid filter such as connectmq/demo/b7d2/#/x to see 0x8F Topic Filter invalid in action. With MQTT.js, the same information is in connack.reasonCode, the granted array of the subscribe callback, and the disconnect event packet.
Debugging Workflow by Reason Code
A practical order for investigating a failure:
- Identify the packet. A code in CONNACK is a connection problem; in SUBACK, a subscription problem; in DISCONNECT, a runtime problem.
- Read the Reason String. It usually narrows the cause to a specific limit or rule.
- Check credentials and identity first for
0x85,0x86and0x87. The guide to fixing MQTT connection refused: not authorized covers this in depth. - Check limits for
0x93,0x95,0x96and0x97: message size, rate, connection count and in-flight messages. - Check fleet hygiene for
0x8E: duplicate client IDs are the most common cause of disconnect loops.
Reason Codes on ConnectMQ
ConnectMQ supports MQTT 3.1.1 and 5.0, so MQTT 5 clients receive the reason codes above. Plan quotas surface as 0x97 Quota exceeded, for example when a free Serverless broker already has its maximum of 25 connections. Plan features matter too: the free plan grants QoS up to 1, so a QoS 2 subscription is granted at a lower QoS in SUBACK, while QoS 2 and shared subscriptions come with the paid Serverless plan. Repeated failed logins are treated as suspicious, and the source IP can be blocked, so never leave a client retrying bad credentials in a tight loop.
For background on what an MQTT broker does with these codes, or the full protocol in context, see the MQTT tutorial.
Debug Faster with ConnectMQ
A managed MQTT broker with an in-browser client and live topic explorer makes it much quicker to reproduce a failing publish or subscription. Create a free Serverless broker at app.connectmq.com, no credit card needed, and test your error handling against real MQTT 5 responses.
Frequently Asked Questions
What is the difference between a reason code and a return code?
Return code is the MQTT 3.1.1 term for the six CONNACK values. MQTT 5 generalised the idea into reason codes that appear in almost every acknowledgement and in DISCONNECT.
Which reason codes mean success?
Any value below 0x80. That includes 0x00 Success, 0x01 and 0x02 for granted QoS, and informational values such as 0x10 No matching subscribers.
What does MQTT reason code 0x97 mean?
0x97 is Quota exceeded: the client hit a limit such as maximum connections or a plan restriction. Reduce usage or move to a plan with higher limits.
Do MQTT 3.1.1 clients receive MQTT 5 reason codes?
No. A 3.1.1 connection only gets the classic CONNACK return codes and the 0x80 SUBACK failure value. Connect with protocol version 5 to receive detailed reason codes.



