You’re trying to see what your MQTT devices are actually saying on the wire, but tcpdump is giving you a wall of hexadecimal.

MQTT runs over TCP, and the default port is 1883. So, to capture that traffic, you need to tell tcpdump to listen on that specific port.

sudo tcpdump -i any port 1883 -vvv

This command will start sniffing on all network interfaces (-i any) for any packets destined for or originating from port 1883 (port 1883), with verbose output (-vvv). You’ll see individual packets, including TCP handshakes, and then the MQTT control packets that make up your messages.

What You’re Actually Seeing

MQTT isn’t just plain text. It’s a binary protocol. When you see tcpdump output, you’re looking at the raw bytes.

A typical MQTT CONNECT packet might look like this in tcpdump:

10:35:01.123456 IP (tos 0, ttl 64, id 12345, offset 0, flags [DF], proto TCP (6), length: 78) 192.168.1.100.51234 > 192.168.1.200.1883: Flags [S], cksum 0x1234, seq 1234567890, win 65535, options [mss 1460,nop,wscale 6,sackOK,TS val 12345678 ecr 0], length 0
10:35:01.123500 IP (tos 0, ttl 64, id 54321, offset 0, flags [DF], proto TCP (6), length: 60) 192.168.1.200.1883 > 192.168.1.100.51234: Flags [S, ACK], cksum 0x5678, seq 0, ack 1234567891, win 65535, options [mss 1460,nop,TS val 98765432 ecr 12345678], length 0
10:35:01.123550 IP (tos 0, ttl 64, id 12346, offset 0, flags [DF], proto TCP (6), length: 93) 192.168.1.100.51234 > 192.168.1.200.1883: Flags [.], cksum 0x9abc, seq 1, ack 1, win 1024, options [nop,nop,TS val 12345679 ecr 98765432], length 33

This is the TCP handshake. After this, you’ll see the MQTT packets. A CONNECT packet, for example, has a specific structure: 0x10 (fixed header for CONNECT), length, "MQTT" string, version, flags, and keep alive.

Deciphering the MQTT Payload

The real magic happens after the TCP handshake. You’ll see packets starting with specific byte patterns.

  • 0x10: CONNECT packet. This is the client telling the broker "I want to connect."
  • 0x20: CONNACK packet. The broker’s response to CONNECT. A 0x00 in the payload means success.
  • 0x30: PUBLISH packet. This is a client sending a message to a topic.
  • 0x40: PUBACK packet. Acknowledgment for a PUBLISH if QoS > 0.
  • 0x50: PUBREC packet. For QoS 2, step 1 of acknowledgment.
  • 0x60: PUBREL packet. For QoS 2, step 2 of acknowledgment.
  • 0x70: PUBCOMP packet. For QoS 2, step 3 of acknowledgment.
  • 0x80: SUBSCRIBE packet. A client asking for messages on a topic.
  • 0x90: SUBACK packet. The broker’s response to SUBSCRIBE.
  • 0xA0: UNSUBSCRIBE packet. A client telling the broker it no longer wants messages.
  • 0xB0: UNSUBACK packet. The broker’s response to UNSUBSCRIBE.
  • 0xC0: PINGREQ packet. Client checking if the connection is still alive.
  • 0xD0: PINGRESP packet. Broker’s response to PINGREQ.
  • 0xE0: DISCONNECT packet. Client or broker gracefully closing the connection.

For a PUBLISH packet (0x30), the bytes that follow will be the topic name (as a UTF-8 string with length prefix) and then the payload itself.

Practical Use Cases

  1. Debugging Connectivity: If your devices aren’t connecting, you can see if the CONNECT packet is even leaving the client, or if the CONNACK is returning from the broker.
  2. Verifying Message Delivery: You can inspect PUBLISH packets to see the exact topic and payload being sent. If a message isn’t arriving at its destination, you can check if it’s being sent correctly and if the broker is acknowledging it.
  3. Understanding QoS: Observing the sequence of PUBLISH, PUBACK, PUBREC, PUBREL, PUBCOMP packets will show you how MQTT’s Quality of Service levels are being handled.
  4. Security Audits: For unencrypted MQTT (which you shouldn’t be doing in production!), you can see sensitive data in plain text. This highlights the critical need for TLS/SSL.

Example: Capturing a PUBLISH

Let’s say a device at 192.168.1.100 publishes to topic sensor/temperature with payload 25.5 to a broker at 192.168.1.200.

sudo tcpdump -i eth0 port 1883 -A

You might see something like:

10:40:05.789123 IP 192.168.1.100.54321 > 192.168.1.200.1883: Flags [P.], seq 123, ack 456, win 1024, options [nop,nop,TS val 12345679 ecr 98765432], length: 35
    0x0000:  30 1f 00 09 73 65 6e 73 6f 72 2f 74 65 6d 70 30  0..sensor/temp0
    0x0010:  2e 35 00 05 48 65 6c 6c 6f                       .5..Hello

The 30 at the beginning is the PUBLISH control packet type. The next byte, 1f (decimal 31), is the remaining length of the packet. Then 00 09 is the length of the topic (9 bytes), followed by the UTF-8 encoded topic sensor/temperature. After that, the payload length is 00 05 (5 bytes), and then the payload 25.5. (Note: In the example above, I’ve shown a hypothetical payload "Hello" and a different topic length to illustrate the structure. A real PUBLISH for sensor/temperature with payload 25.5 would have the correct topic length and payload bytes.)

Advanced Filtering

You can get more specific:

  • Specific client IP: sudo tcpdump -i eth0 host 192.168.1.100 and port 1883 -vvv
  • Specific broker IP: sudo tcpdump -i eth0 host 192.168.1.200 and port 1883 -vvv
  • Only PUBLISH packets: sudo tcpdump -i eth0 port 1883 -X | grep "^0x0000: 30 " (This is a bit of a hack; a true BPF filter for protocol content is complex. -X shows hex and ASCII, making grep easier).

The output can be overwhelming, but by understanding the MQTT packet types and their byte representations, you can decode what’s happening on your MQTT bus.

The next step is often to use a tool that can decode this binary MQTT traffic into human-readable form, like mqtt-spy or mosquitto_sub with logging enabled.

Want structured learning?

Take the full Tcpdump course →