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:CONNECTpacket. This is the client telling the broker "I want to connect."0x20:CONNACKpacket. The broker’s response toCONNECT. A0x00in the payload means success.0x30:PUBLISHpacket. This is a client sending a message to a topic.0x40:PUBACKpacket. Acknowledgment for aPUBLISHif QoS > 0.0x50:PUBRECpacket. For QoS 2, step 1 of acknowledgment.0x60:PUBRELpacket. For QoS 2, step 2 of acknowledgment.0x70:PUBCOMPpacket. For QoS 2, step 3 of acknowledgment.0x80:SUBSCRIBEpacket. A client asking for messages on a topic.0x90:SUBACKpacket. The broker’s response toSUBSCRIBE.0xA0:UNSUBSCRIBEpacket. A client telling the broker it no longer wants messages.0xB0:UNSUBACKpacket. The broker’s response toUNSUBSCRIBE.0xC0:PINGREQpacket. Client checking if the connection is still alive.0xD0:PINGRESPpacket. Broker’s response toPINGREQ.0xE0:DISCONNECTpacket. 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
- Debugging Connectivity: If your devices aren’t connecting, you can see if the
CONNECTpacket is even leaving the client, or if theCONNACKis returning from the broker. - Verifying Message Delivery: You can inspect
PUBLISHpackets 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. - Understanding QoS: Observing the sequence of
PUBLISH,PUBACK,PUBREC,PUBREL,PUBCOMPpackets will show you how MQTT’s Quality of Service levels are being handled. - 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.-Xshows hex and ASCII, makinggrepeasier).
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.