MQTT Fundamentals

MQTT Packet Structure: Fixed Header, Variable Header and Payload

A byte-level walkthrough of MQTT packets: how the fixed header encodes type and flags, how remaining length works, and what the variable header and payload contain, using a CONNECT and PUBLISH you can capture with Wireshark.

Hex dump of an MQTT PUBLISH split into fixed header, variable header and payload to explain mqtt packet structure
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

The MQTT packet structure is deliberately compact: every packet starts with a fixed header of as little as two bytes, followed by an optional variable header and an optional payload. This walkthrough decodes real CONNECT, CONNACK and PUBLISH packets byte by byte, so you can read a Wireshark capture or a hex dump with confidence and spot encoding bugs in your own clients.

The Three Parts of Every MQTT Packet

Every MQTT control packet follows the same layout:

Part Present in Contents
Fixed header All packets Packet type, flags, remaining length
Variable header Most packets Packet-specific fields such as topic name, packet identifier, protocol name, and in MQTT 5, properties
Payload Some packets Client ID and credentials in CONNECT, application message in PUBLISH, topic filters in SUBSCRIBE

The smallest packets, PINGREQ, PINGRESP and an MQTT 3.1.1 DISCONNECT, are just the two-byte fixed header. That economy is a large part of why MQTT works well over slow links, and it is why understanding the structure helps when you estimate MQTT bandwidth usage.

The Fixed Header

The first byte splits into two halves:

Bit:     7  6  5  4 | 3  2  1  0
         packet type | flags

The high four bits hold the packet type, from 1 (CONNECT) to 15 (AUTH). The low four bits are flags. For most packets the flags are fixed, reserved values; only PUBLISH uses them meaningfully:

Bit PUBLISH meaning
3 DUP: this is a redelivery of a QoS 1 or 2 message
2-1 QoS level: 00, 01 or 10
0 RETAIN: broker should store this message

So a first byte of 0x30 is a QoS 0 PUBLISH, 0x32 is QoS 1, 0x31 is a retained QoS 0 message, and 0x3A is a QoS 1 redelivery with DUP set. SUBSCRIBE, UNSUBSCRIBE and PUBREL must have flags 0010, which is why they appear as 0x82, 0xA2 and 0x62. The MQTT control packets reference lists every type with its first byte.

Remaining Length Encoding

After the first byte comes the remaining length: the number of bytes in the variable header plus the payload, excluding the fixed header itself. It uses a variable byte integer, between one and four bytes. Each byte carries seven bits of value, and the top bit says "another byte follows".

Value range Bytes used
0 to 127 1
128 to 16,383 2
16,384 to 2,097,151 3
2,097,152 to 268,435,455 4

Example: a remaining length of 321. Since 321 = 2 x 128 + 65, the first byte is 65 with the continuation bit set (0xC1) and the second byte is 2 (0x02). The four-byte maximum is where the protocol's often-quoted 256 MB packet ceiling comes from; the MQTT message size limit guide explains why real brokers set much lower limits.

Here is a decoder you can use when reading hex dumps:

def decode_remaining_length(data: bytes, offset: int = 1):
    multiplier, value = 1, 0
    for i in range(4):
        byte = data[offset + i]
        value += (byte & 0x7F) * multiplier
        if byte & 0x80 == 0:
            return value, i + 1  # value, number of bytes used
        multiplier *= 128
    raise ValueError("Malformed remaining length")

print(decode_remaining_length(bytes([0x30, 0xC1, 0x02])))  # (321, 2)

Walking Through a Real Session

Run this command to publish one message to the public MQTT test broker with MQTT 3.1.1, which mosquitto_pub uses by default:

mosquitto_pub -h broker.connectmq.com -p 1883 -i demo-a1b2 \
  -t "connectmq/demo/a1b2/t" -m "21.5" -d

Port 1883 is unencrypted, so a packet capture shows every byte. In Wireshark, capture on your network interface and apply the display filter mqtt. Wireshark decodes port 1883 as MQTT automatically; you will see CONNECT, CONNACK, PUBLISH and DISCONNECT. Use your own random topic segment, and remember that anything on the public broker is readable by others.

CONNECT

The client's first packet is 23 bytes:

10 15                         Fixed header: type 1 (CONNECT), remaining length 21
00 04 4D 51 54 54             Protocol name: length 4, "MQTT"
04                            Protocol level: 4 = MQTT 3.1.1
02                            Connect flags: Clean Session
00 3C                         Keep alive: 60 seconds
00 09 64 65 6D 6F 2D 61 31 62 32   Payload: client ID length 9, "demo-a1b2"

The connect flags byte deserves a closer look. Bit 1 is Clean Session, bit 2 Will Flag, bits 3-4 Will QoS, bit 5 Will Retain, bit 6 Password Flag and bit 7 User Name Flag. A client connecting with username and password and clean session would send 0xC2. If those flags are set, the username and password follow the client ID in the payload, each with a two-byte length prefix. This is one reason to always use TLS on port 8883 with real credentials: on port 1883 they travel in clear text.

CONNACK

The broker replies with four bytes:

20 02 00 00

Type 2, remaining length 2, Session Present 0, return code 0 (accepted). Any other final byte is a refusal; the guide to MQTT CONNACK return codes decodes each value.

PUBLISH

The message itself is 29 bytes:

30 1B                         Fixed header: PUBLISH, QoS 0, no retain; remaining length 27
00 15                         Topic length: 21
63 6F 6E 6E 65 63 74 6D 71 2F 64 65 6D 6F 2F 61 31 62 32 2F 74
                              Topic: "connectmq/demo/a1b2/t"
32 31 2E 35                   Payload: "21.5"

Notice what is missing: there is no packet identifier, because QoS 0 messages are never acknowledged. With -q 1, the first byte becomes 0x32, two bytes of packet identifier appear after the topic, and the broker answers with a four-byte PUBACK (40 02 plus the identifier). There is also no payload length field; the payload is simply whatever remains after the variable header, according to the remaining length.

DISCONNECT

Finally the client sends E0 00: type 14, remaining length 0. In MQTT 3.1.1 that is the whole packet.

What Changes in MQTT 5

MQTT 5 keeps the same skeleton but adds properties to the variable header of most packets. A properties section starts with its own variable byte integer length, followed by identifier-value pairs. Even with no properties, the length byte 00 is present.

For the same session over MQTT 5:

  • CONNECT has protocol level 05 and a properties length after keep alive.
  • CONNACK grows to include a reason code and properties, often advertising limits such as Maximum Packet Size, Topic Alias Maximum or Maximum QoS.
  • PUBLISH has a properties length after the topic (and packet identifier), so a property-free QoS 0 publish is one byte larger.
  • DISCONNECT can carry a reason code and properties, which is how the broker explains why it closed a connection.

Properties such as Topic Alias, Message Expiry Interval and User Properties are what enable most MQTT 5 features.

Common Encoding Bugs and How to Spot Them

If you write a custom client or parse MQTT on a gateway, check these first:

  1. Remaining length written as a fixed two-byte field. Works for small packets, breaks above 127 bytes.
  2. String lengths counted in characters instead of bytes. Non-ASCII topic names have more bytes than characters, producing malformed packets.
  3. Missing packet identifier on QoS 1 or 2. The broker reads part of your payload as the identifier and the topic is fine, so the bug looks like corrupted data.
  4. Wrong reserved flags on SUBSCRIBE or PUBREL, which brokers must treat as malformed and close the connection.
  5. Forgetting the empty properties length when switching a client to MQTT 5.

Wireshark marks malformed packets clearly, and an MQTT 5 broker answers with reason code 0x81 Malformed Packet in DISCONNECT, which is much faster to diagnose than a silent close. For the authoritative definitions, see the OASIS MQTT specification.

Inspect Packets Against a Real ConnectMQ Broker

Reading packets is the fastest way to understand MQTT, and the MQTT tutorial builds on this foundation with sessions, QoS and retained messages. ConnectMQ runs the public broker at broker.connectmq.com for exactly this kind of experiment, and private brokers on TLS port 8883 once real data is involved.

Move from Packet Captures to Production with ConnectMQ

When your client is ready, a managed MQTT broker gives you encrypted endpoints, issued credentials and a portal with an in-browser client and live topic explorer. Create a free Serverless broker at app.connectmq.com, no credit card required, or read more about choosing a cloud MQTT broker.

Frequently Asked Questions

How big is the smallest MQTT packet?

Two bytes. PINGREQ, PINGRESP and an MQTT 3.1.1 DISCONNECT consist only of the fixed header: one byte for type and flags and one byte of remaining length set to zero.

What does remaining length include?

It counts the bytes of the variable header and the payload, but not the fixed header itself. It is encoded as a variable byte integer of one to four bytes.

Why can I read my password in Wireshark?

Because port 1883 is unencrypted, and CONNECT carries the username and password in its payload. Use TLS on port 8883 or secure WebSockets on 8084 for any real credentials.

Does the PUBLISH packet include the payload length?

No. The payload length is implied: it is the remaining length minus the size of the variable header, which includes the topic name, the packet identifier for QoS 1 and 2, and properties in MQTT 5.

Related articles

Why Your MQTT Client Keeps Disconnecting (and How to Fix It)

Reconnect loops are one of the most common MQTT problems. Learn the five usual causes, from duplicate client IDs to keep alive timeouts and oversized packets, and follow a diagnosis flow that pinpoints yours.

8 min read

Who Invented MQTT? The History from Oil Pipelines to IoT

MQTT was invented in 1999 by Andy Stanford-Clark of IBM and Arlen Nipper of Arcom to monitor oil pipelines over satellite. Follow its path to open source, OASIS, ISO and today's managed cloud brokers.

7 min read

MQTT Maximum Message Size: Limits and Large Payload Strategies

MQTT can technically carry a 256 MB packet, but brokers, client libraries and devices impose far lower limits. Learn where the real limits are, what happens when you exceed them, and how to send large payloads.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.