MQTT DISCONNECT Reason Codes: Preserve the Difference Between Protocol and Policy Rejection
Keep an MQTT DISCONNECT reason attached to the connection that received it, classify the exact numeric code, and choose the next investigation from that evidence. Separate malformed encoding, unexpected protocol state, unsupported capabilities, administrative limits and transport closure without a reason. Reconnecting unchanged is not a repair for every category.
This guide pins the MQTT 5.0 Standard, 7 March 2019, section 3.14 and error handling in 4.13. Four isolated tests used an actual locally built Mosquitto 2.0.20 broker. Three returned captured reasons; one closed without a DISCONNECT. No production broker or credentials were involved.
Distinguish an omitted reason from an absent packet
A valid DISCONNECT with Remaining Length zero means reason 0x00, Normal disconnection. The property length can also be omitted in the permitted short forms. Let the selected MQTT parser implement these packet-specific defaults.
A socket closing without a DISCONNECT is different. Its MQTT reason is unavailable, not implicitly zero. Likewise, a local connect timeout is not a server-generated “Server busy” result. Store origin, packet presence and reason as separate fields.
Validate the code against the DISCONNECT table and its allowed sending direction. For example, 0x04, Disconnect with Will Message, is client-originated. Reason codes valid in another packet type are not automatically valid in DISCONNECT.

Use categories to choose evidence, not to promise recovery
| Representative reason | First evidence to inspect | Useful corrective boundary |
|---|---|---|
| 0x81 Malformed Packet | Encoded bytes and packet grammar | Repair encoder or rejected input handling |
| 0x82 Protocol Error | Packet order and active protocol phase | Repair state transitions |
| 0x87 Not authorized | Authenticated identity and approved permissions | Correct authorized configuration |
| 0x89 Server busy; 0x8B shutting down | Server availability and operator evidence | Controlled availability recovery |
| 0x93 Receive Maximum exceeded | Outstanding publication ledger and acknowledgements | Correct flow-control accounting |
| 0x94 Topic Alias invalid; 0x95 Packet too large | Negotiated limits and transmitted fields | Correct packet construction or limits |
| 0x97 Quota exceeded; 0x98 Administrative action | Actual imposed limit or administrative decision | Resolve policy, not an assumed transient burst |
| 0x8E Session taken over | Competing connections using the client identifier | Resolve identity ownership |
These are engineering investigation categories. MQTT defines the reason meanings, but it does not guarantee that a particular condition will disappear after a fixed wait. Codes 0x80 and 0x83 especially do not reveal a precise corrective action on their own.
Keep capability failures explicit, including unsupported retained messages, QoS, shared subscriptions, Subscription Identifiers or wildcard subscriptions. Compare CONNECT/CONNACK capabilities and the attempted operation before changing client behavior.
Do not reinterpret valid names as malformed strings
The DISCONNECT descriptions for Topic Filter invalid and Topic Name invalid distinguish a correctly formed value that the receiver does not accept. They are not universal proof of invalid UTF-8 or misplaced wildcard syntax.
Preserve the reason together with a safely represented topic or filter and the relevant policy. If a broker implementation uses an unexpected reason, keep the captured observation and consult its version-specific behavior rather than rewriting the log to match an assumed diagnosis.
Four executed broker observations
The fixture started a fresh loopback-only broker with anonymous access restricted to synthetic tests, persistence disabled, max_topic_alias one and max_packet_size eighty bytes. TLS, WebSockets and dynamic plugins were disabled in the local build. Each ordinary case first completed a valid MQTT 5 CONNECT/CONNACK exchange.
| Injected laboratory condition | Captured result |
|---|---|
| PUBLISH with Topic Alias 65,535, above the configured maximum | DISCONNECT 0x94 |
| PUBLISH exceeding the eighty-byte packet limit | DISCONNECT 0x95 |
| Second CONNECT on an established connection | Connection closed; no DISCONNECT packet received |
| Another connection using the same client identifier | Original connection received DISCONNECT 0x8E |
The second-CONNECT case is important: the initial test expectation of a numeric Protocol Error was not observed. The corrected fixture asserts the actual silent closure, rather than inventing 0x82 from the likely protocol violation.
These results describe this build and test configuration. They do not establish every broker’s choice of diagnostics. Authorization, overload, redirection and rate-limit cases were not executed, and the synthetic raw client is not a complete MQTT implementation.
Retain machine policy independently of human text
Reason String is optional human-readable diagnostic information and should not be parsed into machine control decisions. It can be absent, including because adding it would exceed the peer’s packet-size limit. Preserve a bounded, sanitized copy when useful, with the numeric reason as the stable input.
For 0x9C, Use another server, and 0x9D, Server moved, preserve any Server Reference as redirection information. Apply the deployment’s approved endpoint and trust policy before following it; receiving a reference is not permission to send credentials to an arbitrary destination.
After an endpoint sends DISCONNECT, it must send no further MQTT control packets on that network connection and must close it. Ensure queued writers cannot continue using the old connection after the protocol engine has entered its closing state.
Build a connection-ending diagnostic ledger
- Assign a connection instance and preserve the negotiated limits and capabilities.
- Record who observed or initiated closure, and whether a valid MQTT reason packet was received.
- Retain the exact numeric reason and permitted sender direction without substituting local guesses.
- Attach the smallest relevant packet, state or policy evidence to its category.
- Exercise controlled refusal fixtures in an isolated deployment and compare wire reasons with client callbacks.
- Verify that silent closure and unknown codes remain distinguishable from normal disconnection.
The acceptance result is a diagnosable connection failure with an evidence-based next action. It does not establish whether earlier application commands executed, and it must not convert a connection-level reason into a success or failure result for every outstanding business operation.