Safeguard
Supply Chain Security•

MQTT 5 Explained: Packets, QoS, Sessions and Broker Security

MQTT is a publish/subscribe protocol where clients talk only to a broker over TCP. How its 15 packets, QoS levels and sessions work, and how to secure it.

Safeguard Security Team
33 min read

MQTT is a lightweight publish/subscribe messaging protocol in which devices and applications never talk to each other directly: every client opens a TCP connection to a broker, publishes messages to named topics and subscribes to the topics it cares about, and the broker does the routing. The current version is MQTT 5.0, an OASIS Standard dated 7 March 2019, which replaced MQTT 3.1.1 (OASIS, 29 October 2014, also published as ISO/IEC 20922:2016).

This post walks through the protocol the way a broker sees it: the 15 control packet types, connection setup and keep alive, what QoS 0, 1 and 2 really promise, retained and will messages, session expiry, and what changed between 3.1.1 and 5.0. Then it covers what MQTT guarantees on its own (very little), the weakness classes that follow, and the Mosquitto settings and commands a defender uses to check a broker they run. Three narrower posts already exist and are linked rather than repeated: what the MQTT and OPC UA specs say about security, MQTT broker limits and MQTT topic ACL patterns per device.

Key takeaways

  • MQTT is an application layer protocol over TCP, TLS or WebSocket. The 5.0 standard says connectionless transports such as UDP "are not suitable on their own" because they can lose or reorder data.
  • IANA registers TCP port 1883 for MQTT and 8883 (service name secure-mqtt) for MQTT over TLS. The standard strongly recommends 8883 for servers that offer TLS.
  • Every packet starts with a 4 bit type, 4 bits of flags and a Remaining Length of 1 to 4 bytes, capping a packet at 268,435,455 bytes. MQTT 5.0 has 15 packet types; 3.1.1 has 14 and reserves value 15, which 5.0 uses for AUTH.
  • QoS 0 is at most once, QoS 1 at least once, QoS 2 exactly once. The guarantee is per hop, between one sender and one receiver, not end to end.
  • MQTT 5.0 added reason codes on all acknowledgements, 27 properties, session and message expiry, shared subscriptions, topic aliases, flow control and an AUTH packet for challenge and response authentication.
  • MQTT has no encryption, integrity protection or server authentication of its own. The standard leaves those to TLS, VPNs or the application, and asks brokers to authorise each client's use of its Client Identifier and to restrict broad filters such as #.
  • In Eclipse Mosquitto 2.x, allow_anonymous defaults to false and a broker started without a config file listens only on loopback. Most exposure comes from configuration that undoes those defaults.

What MQTT is for, the layer and the standard

MQTT moves small messages between many devices and the services that care about them, over networks that may be slow or unreliable, with little code and bandwidth. The OASIS abstract calls it "light weight, open, simple, and designed to be easy to implement", aimed at machine to machine and IoT contexts "where a small code footprint is required and/or network bandwidth is at a premium".

The publish/subscribe model with a broker

A temperature sensor publishes a reading to a topic such as plant1/line3/temp without knowing who reads it. A dashboard, a historian and an alerting service each subscribe to that topic, or to a filter that matches it, and the broker delivers a copy to each. Publishers and subscribers never connect to each other and need not be online at the same time if sessions hold messages for them.

The standard calls the broker the "Server" and every device or application a "Client". A compromised client can do only what the broker allows; a compromised broker can do anything to everyone. The standard also notes that clients sharing a server have "a transitive trust relationship" with other clients that may publish on the same topics.

Topics are UTF-8 strings split into levels by /. Subscription filters may use two wildcards: + matches exactly one level and # matches that level and everything below it, so sport/tennis/+ matches sport/tennis/player1 but not sport/tennis/player1/ranking. Topics starting with $ are reserved: a server must not match filters that begin with a wildcard against them, so a subscription to # does not receive $SYS/ broker statistics. A client must ask for $SYS/# explicitly.

Where it sits in the stack

MQTT needs a transport providing "ordered, lossless, bi-directional connections", and the standard names TCP, TLS and WebSocket. Over WebSocket, packets travel in binary frames and the client must offer the subprotocol mqtt. A separate specification, MQTT-SN version 1.2, adapts the model to non TCP/IP networks such as Zigbee and is out of scope here. Everything in TCP vs UDP: what an eavesdropper can see and change about plaintext TCP applies to MQTT on 1883.

The defining standards

VersionPublisher and dateStatus today
MQTT V3.1IBM protocol specification, referenced by OASISHistorical; mqtt.org keeps it "for historical reference"
MQTT 3.1.1OASIS Standard, 29 October 2014; Errata 01 approved 10 December 2015Widely deployed; mqtt.org calls it "an older ISO and OASIS Standard"
ISO/IEC 20922:2016ISO/IEC publication of MQTT 3.1.1Same protocol as OASIS 3.1.1
MQTT 5.0OASIS Standard, 7 March 2019Current; "replaces or supersedes" 3.1.1

Both were produced by the OASIS MQTT Technical Committee and point to a companion note, "MQTT and the NIST Cybersecurity Framework Version 1.0". A broker can serve both versions on one port and tell them apart by the Protocol Version byte in CONNECT: 4 for 3.1.1, 5 for 5.0.

How MQTT works on the wire

The packet format

Every control packet has a fixed header, then optionally a variable header and a payload.

PartSizeContents
Byte 1, bits 7 to 44 bitsControl packet type, 1 to 15
Byte 1, bits 3 to 04 bitsFlags. PUBLISH uses DUP, QoS and RETAIN; PUBREL, SUBSCRIBE and UNSUBSCRIBE must send 0010; all others 0000
Remaining Length1 to 4 bytesLength of variable header plus payload, as a Variable Byte Integer
Variable headerVariesPacket Identifier where acknowledgement is needed; in 5.0 also a Reason Code and properties
PayloadVariesRequired in CONNECT, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK; optional in PUBLISH

Each Remaining Length byte carries seven bits of value and a continuation bit, with at most four bytes, so the ceiling is 268,435,455 bytes; the 3.1.1 standard calls it "256 MB". That is a protocol maximum, not a sensible broker limit, which is why 5.0 adds a Maximum Packet Size property. A reserved flag bit set wrongly makes a Malformed Packet, and a server that detects one "MUST close the Network Connection", in 5.0 optionally after reason code 0x81 (Malformed Packet) or 0x82 (Protocol Error).

The 15 control packet types

ValuePacketDirectionPurpose
0ReservedForbiddenNever valid
1CONNECTClient to serverOpen a session and authenticate
2CONNACKServer to clientAccept or refuse the connection
3PUBLISHBothCarry an application message
4PUBACKBothAcknowledge a QoS 1 PUBLISH
5PUBRECBothQoS 2 step 1: publish received
6PUBRELBothQoS 2 step 2: publish release
7PUBCOMPBothQoS 2 step 3: publish complete
8SUBSCRIBEClient to serverRequest subscriptions
9SUBACKServer to clientGrant or refuse each subscription
10UNSUBSCRIBEClient to serverRemove subscriptions
11UNSUBACKServer to clientConfirm removal
12PINGREQClient to serverKeep alive probe
13PINGRESPServer to clientKeep alive answer
14DISCONNECTBoth in 5.0; client to server in 3.1.1Close the connection, with a reason in 5.0
15AUTHBothEnhanced authentication (5.0 only)

CONNECT and CONNACK

CONNECT is the first packet on a new connection. Its variable header carries the protocol name MQTT, the Protocol Version, a Connect Flags byte, a two byte Keep Alive in seconds and, in 5.0, properties.

BitConnect flagMeaning
7User Name FlagUser name present in payload
6Password FlagPassword present in payload
5Will RetainPublish the will as retained
4 and 3Will QoSQoS for the will
2Will FlagA will message is present
1Clean Start (5.0) or Clean Session (3.1.1)Discard any existing session
0ReservedMust be 0

The payload carries the Client Identifier first, then the will fields, user name and password if flagged. A server must accept Client Identifiers of 1 to 23 letters and digits and may accept more. The server "SHOULD perform authentication and authorization checks" and must close the connection if they fail. If they pass and the Client Identifier belongs to a client already connected, the server disconnects the existing client (in 5.0 with reason code 0x8E, Session taken over) and the newcomer takes over the session. That rule lets a device reconnect cleanly after a silent network drop; it is also the root of the client ID takeover weakness below.

CONNACK carries a Session Present flag and a result. In 3.1.1 it is a return code 0 to 5 (0x04 bad user name or password, 0x05 not authorized). In 5.0 it is a Reason Code plus properties announcing server limits such as Receive Maximum, Maximum QoS, Retain Available, Maximum Packet Size and Shared Subscription Available.

Keep Alive is the longest silence the client promises; when idle it sends PINGREQ and expects PINGRESP. If the server hears nothing "within one and a half times the Keep Alive time period" it must close the connection. A value of 0 disables the check, and in 5.0 the server can impose its own value with Server Keep Alive.

A connection from start to finish

Client                                        Broker
  |  TCP handshake (and TLS handshake on 8883)  |
  |-------------------------------------------->|
  |  CONNECT ver=5 ClientID=sensor-17           |
  |          CleanStart=0 KeepAlive=60          |
  |          SessionExpiry=3600 Will=on         |
  |-------------------------------------------->|
  |        CONNACK rc=0x00 SessionPresent=1     |
  |<--------------------------------------------|
  |  SUBSCRIBE id=1 plant1/line3/cmd (QoS 1)    |
  |-------------------------------------------->|
  |        SUBACK id=1 rc=0x01 (granted QoS 1)  |
  |<--------------------------------------------|
  |  PUBLISH QoS1 id=2 plant1/line3/temp        |
  |-------------------------------------------->|
  |        PUBACK id=2 rc=0x00                  |
  |<--------------------------------------------|
  |  PINGREQ (after 60 s idle)                  |
  |-------------------------------------------->|
  |        PINGRESP                             |
  |<--------------------------------------------|
  |  DISCONNECT rc=0x00 (normal, will deleted)  |
  |-------------------------------------------->|

PUBLISH and SUBSCRIBE

PUBLISH carries a Topic Name (no wildcards allowed), a Packet Identifier if QoS is above 0, properties in 5.0, and an opaque payload; the standard calls MQTT "agnostic to the content of the payload". SUBSCRIBE carries a list of filters with options. In 3.1.1 the only option is maximum QoS; 5.0 adds No Local, Retain As Published and Retain Handling. SUBACK returns one result per filter: the granted QoS or 0x80 in 3.1.1, a reason code such as 0x87 (Not authorized) in 5.0.

QoS 0, 1 and 2

The standard says delivery "is concerned solely with the delivery of an Application Message from a single sender to a single receiver". The publisher's QoS governs the hop to the broker and the subscription's granted QoS governs the hop to the subscriber, so the outbound level "could differ from that of the inbound Application Message".

QoSNamePacketsGuaranteeCost
0At most oncePUBLISHOnce or not at all; no acknowledgement or retryOne packet
1At least oncePUBLISH, PUBACKDelivered, duplicates possibleTwo packets; sender stores until PUBACK
2Exactly oncePUBLISH, PUBREC, PUBREL, PUBCOMPNo loss or duplication on this hopFour packets; state on both sides

For QoS 1, once the receiver has sent PUBACK it treats any later PUBLISH with that identifier as new, so a resend after a lost PUBACK produces a duplicate that applications must handle. QoS 2 removes duplicates with a two step handshake:

Sender                                   Receiver
  | PUBLISH QoS2 id=7 (keeps message)       |
  |---------------------------------------->|  stores id=7
  |                  PUBREC id=7            |
  |<----------------------------------------|
  | PUBREL id=7 (drops message, keeps id)   |
  |---------------------------------------->|  delivers onward once
  |                  PUBCOMP id=7           |
  |<----------------------------------------|

Until PUBREL arrives, the receiver answers repeats with PUBREC and "MUST NOT cause duplicate messages to be delivered to any onward recipients". In 5.0, Receive Maximum caps how many QoS 1 and 2 messages may be unacknowledged at once. None of this covers the application: exactly once does not mean the subscriber processed or stored the message.

Retained messages

A PUBLISH with RETAIN set makes the broker "replace any existing retained message for this topic and store the Application Message", then send it to future matching subscribers. Each topic holds one retained message, and an empty retained payload deletes it. That is how a new dashboard learns every device's last state at once. It also means retained messages are data at rest on the broker, handed to anyone allowed a matching filter.

Will messages

A client can register a will in CONNECT: a topic, payload, QoS and retain flag the broker publishes for it if the connection ends abnormally, such as a network failure, a missed Keep Alive, or a close without DISCONNECT reason 0x00. A normal DISCONNECT deletes the will. MQTT 5.0 adds a Will Delay Interval; if the client reconnects within it, the will is not sent, so brief blips do not flood subscribers with offline notices.

Sessions and session expiry

A session outlives one TCP connection. On the server it holds subscriptions, unacknowledged QoS 1 and 2 messages, messages queued while the client was away, and the will. MQTT 3.1.1 had one bit, Clean Session. MQTT 5.0 splits it: Clean Start says whether to discard existing state at connect, and the Session Expiry Interval says how many seconds to keep the session after disconnect. Absent or 0 ends the session with the connection; 0xFFFFFFFF means it never expires. The standard notes that Clean Start 1 with expiry 0 equals 3.1.1 Clean Session 1, and Clean Start 0 with no expiry equals Clean Session 0. The interval can be changed at DISCONNECT, and a client that no longer needs its session should disconnect with expiry 0. CONNACK's Session Present flag tells the client whether to resubscribe.

Versions and history: what changed from 3.1.1 to 5.0

The 5.0 standard lists its goals as scalability, better error reporting, formalising patterns such as capability discovery and request and response, extensibility through user properties, and performance for small clients. Appendix C summarises the changes:

AreaMQTT 3.1.1MQTT 5.0
ErrorsCONNACK codes 0 to 5; SUBACK 0x80Reason code on CONNACK, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, UNSUBACK, DISCONNECT, AUTH, plus optional Reason String
Server closeServer drops TCPServer DISCONNECT with a reason and optional Server Reference redirect
MetadataNone27 properties, including User Properties
SessionsClean Session bitClean Start plus Session Expiry Interval
Message lifetimeUntil deliveredMessage Expiry Interval
Load balancingNot standardisedShared subscriptions
BandwidthFull topic in every PUBLISHTopic Alias, a two byte integer
Flow controlNoneReceive Maximum
CapabilitiesImplicitMaximum Packet Size, Maximum QoS, Retain Available and more in CONNACK
AuthenticationUser name and passwordSame, plus AUTH challenge and response and re-authentication
Request and responseConventionResponse Topic and Correlation Data

Reason codes and properties

Codes below 0x80 mean success and 0x80 and above failure. Ones defenders see in logs include 0x86 Bad User Name or Password, 0x87 Not authorized, 0x8E Session taken over, 0x95 Packet too large and 0x97 Quota exceeded. The Reason String "is not intended to be parsed by the receiver". Properties are typed fields in the variable header; 5.0 defines 27, each currently identified by one byte. User Properties are arbitrary string pairs MQTT forwards without meaning.

Enhanced authentication and AUTH

MQTT 3.1.1 has only the user name and password fields, which the standard notes can carry other things such as a token. MQTT 5.0 adds enhanced authentication: the client names an Authentication Method in CONNECT ("commonly a SASL mechanism") and both sides exchange AUTH packets with reason code 0x18 (Continue authentication) until the server sends CONNACK with code 0. The standard's examples use SCRAM-SHA-1 and Kerberos.

Client                                         Broker
  | CONNECT AuthMethod=SCRAM-SHA-1 AuthData=client-first
  |--------------------------------------------->|
  |          AUTH rc=0x18 AuthData=server-first  |
  |<---------------------------------------------|
  | AUTH rc=0x18 AuthData=client-final           |
  |--------------------------------------------->|
  |          CONNACK rc=0x00 AuthData=server-final
  |<---------------------------------------------|

A client can later send AUTH with reason 0x19 to re-authenticate on the same connection, which lets brokers refresh short lived credentials. Enhanced authentication is optional on both sides, but it is the only route inside MQTT to mutual authentication.

Shared subscriptions

A shared subscription uses the filter form $share/{ShareName}/{filter}, and each matching message goes to only one session in the group. Different share names on the same filter are independent groups. This is how you scale consumers behind a busy topic.

What is still deployed

Both versions are in active use, and brokers routinely serve 3.1.1 and 5.0 clients side by side. 3.1.1 clients get none of the 5.0 signalling: Mosquitto notes it enforces its maximum packet size on all clients but "it is not possible to notify clients using MQTT v3.1.1 or MQTT v3.1 of the limit".

Security properties: what MQTT does and does not guarantee

The 5.0 Security chapter is non-normative. It says MQTT leaves implementers "a choice of network, privacy, authentication and authorization technologies" and that it is "the implementer's responsibility" to include the right ones.

PropertyMQTT by itselfWhere it comes from
ConfidentialityNone; credentials and payloads are cleartext on 1883TLS on 8883, VPN, or payload encryption (topic names stay visible)
IntegrityNothing beyond TCP's checksumTLS, VPN, or application hashes and signatures
Client authenticationOptional user name and password; optional AUTH in 5.0Broker password store, client certificates, external identity
Server authenticationNone with basic authentication; MQTT "is not trust symmetrical"TLS certificate validation or a mutual AUTH method
AuthorisationNot defined, only recommendedBroker ACLs and plugins
Replay protectionNoneTLS
AvailabilityKeep Alive; 5.0 Receive Maximum and Maximum Packet SizeBroker limits, rate limiting, network controls

The standard's threat list includes compromised devices, data at rest, timing side effects, denial of service, interception or alteration, and "injection of spoofed MQTT Control Packets". It describes layered profiles: clear communication, a secured network such as a VPN, and a secured transport using TLS "which provides authentication, integrity and privacy". Our MQTT and OPC UA specs post reads that chapter section by section.

Weakness classes, explained as concepts

These are the recurring ways deployments go wrong, explained as concepts so you can recognise them in your own systems.

Anonymous brokers on the internet

MQTT does not require authentication. A broker that accepts anonymous connections on a public address lets anyone subscribe to #, receive every retained message, and publish to any topic not otherwise protected, which matters when devices act on command topics. Mosquitto changed its default for this reason; its documentation records that in 1.6.x and earlier allow_anonymous "defaulted to true unless there was another security option set".

Wildcard subscription overreach

An authenticated client with a broad filter can be almost as revealing as an anonymous one. A device that needs only devices/sensor-17/cmd but may subscribe to devices/# reads every other device's traffic. The standard asks implementations to "consider limiting access to Topic Filters that have broad scope, such as the # Topic Filter". The same applies to $SYS/#. Per device ACLs are the fix, covered in our topic ACL patterns post.

Client ID takeover

Because a CONNECT with an in-use Client Identifier makes the broker drop the existing client and hand over its session, the identifier acts as a key to that session's subscriptions and queued messages. The standard says implementations "should check that the Client is authorized to use the Client Identifier as this gives access to the MQTT Session State", guarding against a client that "accidentally or maliciously" reuses one. Accidents are common: a firmware image with a hard coded client ID produces a fleet that keeps knocking itself offline.

Cleartext on port 1883

On 1883, user names, passwords, tokens, topics and payloads are readable and alterable by anyone on the path. The standard warns that cleartext, obfuscated or absent credentials "can give rise to Man-in-the-Middle and replay attacks", and that TLS cipher suites with NULL encryption must be avoided. A client that does not validate the server certificate against the expected host name gets encryption without knowing who it is talking to.

Transitive trust and hostile payloads

Subscribers trust what the broker forwards. The standard's example: if the server does not validate UTF-8 but a subscriber's library does, a publisher can send a disallowed code point in a topic or property and the subscriber may disconnect, then receive the same QoS 1 or 2 message again on reconnect. The wider lesson is to treat every MQTT payload as untrusted input in the consuming service.

Resource exhaustion and vulnerable implementations

Large packets, deep offline queues, never expiring sessions and idle connections consume broker memory; the standard suggests watching for repeated connection attempts, topic scanning and clients that connect but never send, and our broker limits post covers the controls. Separately, MQTT parsers sit at network edges and have had bugs like any network code. An old broker in a container image or a client library pinned years ago in a service is a dependency inventory problem, covered in "Fix it without waiting for upstream" below.

Secure configuration, with defender side check commands

These examples use Eclipse Mosquitto. Commands come from the Mosquitto man pages (mosquitto, mosquitto.conf, mosquitto_sub, mosquitto_passwd), the OpenSSL s_client manual and the ss(8) man page, all listed in Sources. Only run them against brokers and hosts you own or are authorised to test, and confirm options against the documentation for your version; the current man pages describe options added in 2.1.

1. Bind listeners deliberately

The mosquitto man page says that without a config file "the broker will listen on port 1883 bound to the loopback interface". Exposure starts when a listener line is added, so give each one a bind address (or use bind_interface):

# Plain MQTT for local processes only
listener 1883 127.0.0.1

# TLS listener for the device network
listener 8883 10.20.0.5
certfile /etc/mosquitto/certs/broker.crt
keyfile  /etc/mosquitto/certs/broker.key
cafile   /etc/mosquitto/ca_certificates/ca.crt
tls_version tlsv1.2

Then check what is listening (ss(8): -t TCP, -l listening, -n numeric, -p processes):

sudo ss -tlnp | grep -E ':(1883|8883)\b'

A socket on 0.0.0.0:1883 or [::]:1883 is reachable on every interface.

2. Validate before restarting

Mosquitto 2.1 added a mode that will "Load the config file specified with -c, and verify that it is valid but do not start the broker":

mosquitto -c /etc/mosquitto/mosquitto.conf --test-config

3. Require authentication

allow_anonymous defaults to false, "unless no listeners are defined in the configuration file, in which case it set to true, but connections are only allowed from the local machine". Never set it to true on a network listener; listener_allow_anonymous controls it per listener. Create a password file (you are prompted for the password):

sudo mosquitto_passwd -c /etc/mosquitto/passwd sensor-17

The man page lists argon2id, sha512-pbkdf2 and sha512 hashes and says it "Defaults to argon2id". It warns that batch mode puts the password on the command line, visible in process lists and shell history. Mosquitto recommends its Dynamic Security plugin for larger fleets. Then confirm anonymous access is refused; this should fail to connect rather than print a message (-C exits after a count, -W is a timeout in seconds, -d shows debug output):

mosquitto_sub -h broker.example.internal -p 1883 -t '$SYS/broker/version' -C 1 -W 5 -d

4. Use client certificates

With require_certificate true, "a client connecting to this listener must provide a valid certificate in order for the network connection to proceed". use_identity_as_username true makes the certificate Common Name the user name, but the documentation warns that "any CA certificates you include in cafile or capath will be able to issue client certificates that are valid for connecting to your broker". Use a dedicated device CA, and a crlfile for revocation.

5. Pin TLS and check it from outside

tls_version sets the minimum (tlsv1.3 or tlsv1.2); unset, it allows both. Verify with OpenSSL, where -verify_return_error aborts on verification errors, -servername sets SNI and -brief prints a summary:

openssl s_client -connect broker.example.internal:8883 \
  -servername broker.example.internal \
  -CAfile /etc/mosquitto/ca_certificates/ca.crt \
  -verify_return_error -brief </dev/null

# With tls_version tlsv1.3, a TLS 1.2 only handshake should fail
openssl s_client -connect broker.example.internal:8883 -tls1_2 -brief </dev/null

Then test a real MQTT 5 login over TLS with a monitoring account (-V selects 5, 311 or 31):

mosquitto_sub -h broker.example.internal -p 8883 \
  --cafile /etc/mosquitto/ca_certificates/ca.crt \
  -V 5 -u monitor -t '$SYS/broker/version' -C 1 -W 5

mosquitto_sub's own TLS default is tlsv1.2. Avoid its insecure option outside first time testing; the man page says it disables server host name verification.

6. Write least privilege ACLs

With acl_file set, "only the topics listed will have access". Lines use topic, user and pattern, with %c for client ID and %u for user name, and deny is evaluated before grants:

# Each device writes its own telemetry and reads its own commands
pattern write devices/%u/telemetry
pattern read  devices/%u/cmd

user ingest-service
topic read  devices/+/telemetry
topic write devices/+/cmd

The user in an ACL "is the same as in password_file. It is not the clientid", so bind client IDs to identities separately. Current docs suggest the mosquitto_acl_file plugin over the acl_file option.

7. Client IDs, $SYS, logging and limits

allow_zero_length_clientid defaults to true; fleets with fixed identities can disable it. sys_interval 0 disables $SYS "completely"; otherwise restrict $SYS/# to monitoring accounts. connection_messages true logs connects and disconnects. max_packet_size "Defaults to 2000000 bytes since 2.1. Earlier versions defaulted to no limit." max_keepalive defaults to 0, which lets clients disable keep alive.

Where NIST guidance fits

NIST IR 8259A (May 2020) defines an IoT device cybersecurity capability core baseline: device identification, device configuration, data protection, logical access to interfaces, software update and cybersecurity state awareness. A unique per device certificate and client ID serve identification, refusing anonymous and cleartext access serves logical access, and software update is how a vulnerable MQTT library in firmware gets replaced. NIST IR 8259 covers the foundational activities for manufacturers.

When to use MQTT versus the alternatives

ProtocolModel and transportBest fitSecurity modelOperational cost
MQTT 5.0 / 3.1.1Pub/sub through a broker over TCP, TLS or WebSocketFrequent small telemetry and commands, fan out, unreliable linksTLS plus broker authentication and ACLsA broker to run and secure; ACL design
AMQP 1.0Messaging with links and flow control over TCP or TLSService to service messaging, richer delivery semanticsSASL and TLS in the connectionHeavier clients, more concepts
CoAPREST over UDP with observeVery constrained devices, lossy low power networksDTLS or OSCOREEvery device is a server; open UDP endpoints risk reflection
HTTP/HTTPSRequest and response over TCP or QUICOccasional uploads, firmware, management APIsTLS plus web authenticationVerbose, polling rather than push

Choose MQTT when many devices push small messages to a central system that fans them out and you can run a broker well. Choose CoAP when TCP and TLS are too heavy, AMQP for richer server side messaging, and HTTPS for anything occasional or large.

Common mistakes

  • Treating QoS 2 as end to end. It is per hop, and a QoS 0 subscriber receives at QoS 0.
  • Adding a listener without a bind address, then assuming the loopback default still applies.
  • Setting allow_anonymous true to fix a connection problem, which removes authentication instead.
  • Running TLS on 8883 while 1883 stays open on the same broker.
  • Skipping certificate validation in clients, which gives encryption without knowing the peer.
  • One client ID or one user name for a whole fleet, which breaks sessions and makes per device ACLs meaningless.
  • Granting # to service accounts that need one subtree.
  • Session Expiry 0xFFFFFFFF for transient clients, which keeps sessions and queues forever.
  • Assuming payload encryption hides topics or forgetting retained messages are stored data.

FAQ

What port does MQTT use?

TCP 1883 for plain MQTT and TCP 8883 for MQTT over TLS, registered with IANA as mqtt and secure-mqtt (the registry also lists UDP entries for both). Over WebSocket, MQTT uses whatever port the web listener uses.

Is MQTT encrypted?

No. Use TLS on 8883 or a VPN, and encrypt payloads in the application if they must stay confidential on the broker. Topic names stay readable to the broker either way.

What is the difference between MQTT 3.1.1 and MQTT 5?

MQTT 5.0 keeps the packet structure and adds reason codes, properties, session and message expiry, shared subscriptions, topic aliases, flow control, server DISCONNECT with redirects and the AUTH packet. One broker can serve both.

What are MQTT QoS levels?

QoS 0 is at most once, QoS 1 at least once (PUBLISH, PUBACK) with possible duplicates, QoS 2 exactly once (PUBLISH, PUBREC, PUBREL, PUBCOMP). Each applies to one hop.

How many packet types does MQTT have?

MQTT 5.0 defines 15, numbered 1 to 15 with 0 reserved. MQTT 3.1.1 defines 14 and reserves 15.

What is a retained message in MQTT?

The last message published with RETAIN on a topic, stored by the broker and sent at once to new matching subscribers. An empty retained payload deletes it.

MQTT vs HTTP: which is better for IoT?

MQTT for frequent small messages and push to devices through a broker; HTTP for occasional requests, large transfers and existing web infrastructure. Many systems use both.

Is an MQTT broker safe to expose to the internet?

Only with TLS, mandatory authentication, authorisation of client IDs and topics, and resource limits. Reaching the broker over a VPN or private network removes most of the exposure.

Fix it without waiting for upstream

MQTT security problems that are not configuration usually live in code you depend on: the broker image in your cluster, or the MQTT client library compiled into a backend service or device firmware. Safeguard's software composition analysis, generally available, inventories those libraries with their full transitive dependencies, alongside SBOM generation in SPDX and CycloneDX and container image scanning; vulnerability findings are enriched with EPSS and CISA KEV data and ranked with reachability analysis, all generally available. Safeguard does not inspect, filter or block MQTT traffic. What it does is tell you which broker and client versions you ship, and help you replace the vulnerable ones quickly.

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

These demos show dependency, SBOM and remediation workflows, not MQTT traffic monitoring. You can download Safeguard and follow the CLI docs to try them on your own projects.

This demo sets up the Safeguard runner on your own machine, approves it with its device code and then scans from the terminal. Pointed at the container image for your broker, or at a backend service that ingests device telemetry, the same scan lists the MQTT broker and client libraries inside with their versions and any known vulnerabilities.

Click through it at your own pace, or open it full screen.

This demo is a real CLI session: install, open the interactive terminal, ask it to fix the lodash CVE in a repository, and watch it read the finding and advisory, ask before each command, move lodash to 4.17.21, run npm install and npm test with approval, and print a summary without pushing anything. The same approve each step flow applies when the package to bump is an MQTT client library pinned in a service's manifest.

Click through it at your own pace, or open it full screen.

This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask in plain language which of your projects include a particular MQTT library version, and get the answer from your own scan data rather than from memory.

Click through it at your own pace, or open it full screen.

Quick check

A sensor publishes at QoS 2 and a dashboard subscribes at QoS 0. What does the dashboard get?

QoS 0 delivery. MQTT QoS is per hop: the publisher's level governs the hop to the broker and the subscription's granted level governs the hop to the subscriber. The dashboard receives each message at most once, with no acknowledgement or retry on that hop.

Your broker uses TLS and passwords. Why should it still check which user may use which client ID?

Because a CONNECT with a client ID that is already connected makes the broker disconnect the existing client and hand the session to the newcomer. Without a check that ties client IDs to authenticated identities, any valid user can knock another device offline or inherit its queued messages. The MQTT 5.0 standard recommends exactly this authorisation check.

Sources

Last reviewed: 23 October 2026. Broker option names and defaults change between releases; check the documentation for the Mosquitto version you run.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.