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.

Closure evidence first separates a valid MQTT DISCONNECT from transport closure without one. A valid packet yields a checked numeric reason and category; missing packets preserve unavailable reason. Four actual Mosquitto cases show alias invalid94, packet too large95, Session taken over8E and a second CONNECT closing without a reason packet.
Actual loopback observations are kept separate from recommended diagnostic categories. A silent close does not supply a numeric MQTT reason.

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

  1. Assign a connection instance and preserve the negotiated limits and capabilities.
  2. Record who observed or initiated closure, and whether a valid MQTT reason packet was received.
  3. Retain the exact numeric reason and permitted sender direction without substituting local guesses.
  4. Attach the smallest relevant packet, state or policy evidence to its category.
  5. Exercise controlled refusal fixtures in an isolated deployment and compare wire reasons with client callbacks.
  6. 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.

Similar Posts