Broker Testing

Reading MQTT Broker Logs to Debug Clients

How to read Mosquitto and EMQX logs: set the right log levels, decode connect and disconnect lines, trace one client and match log messages to the fix.

Updated · 9 min read

Reading MQTT Broker Logs to Debug Clients

When a client says connection lost, it usually has no idea why. The broker does. Every refused login, keep-alive timeout, duplicate client ID and denied publish leaves a line in the broker log — if logging is configured to keep it. Reading those lines is often the fastest way to debug a client you cannot attach a debugger to.

This guide shows where to find the logs, which log levels to turn on, how to read Mosquitto's connect and disconnect lines, how to trace one client in EMQX, and how to turn a log line into a fix.

Where the logs are

SetupWhere to look
Mosquitto from a Linux package/var/log/mosquitto/mosquitto.log or journalctl -u mosquitto -f
Mosquitto in Dockerdocker logs -f mosquitto (with log_dest stdout)
EMQXConsole output in Docker (docker logs -f emqx); files in the log/ directory of the installation
HiveMQlog/hivemq.log, plus log/event.log for client connect and disconnect events
Managed brokerUsually no raw broker logs — rely on reason codes and monitoring (see below)

If the Mosquitto log file stays empty, check the startup output for Error: Unable to open log file /var/log/mosquitto/mosquitto.log for writing. — the broker user cannot write to the directory. Logging setup in depth is covered in Mosquitto logging.

Turn on the right log level

Mosquitto's default logging hides most of what you need for client debugging. A useful setup while you investigate:

mosquitto.conf — logging while debugging
log_dest stdout
log_dest file /var/log/mosquitto/mosquitto.log

log_type error
log_type warning
log_type notice
log_type information
log_type subscribe
log_type unsubscribe
# Every packet in and out — very verbose, enable only while debugging
log_type debug

connection_messages true
log_timestamp true
log_timestamp_format %Y-%m-%dT%H:%M:%S

Restart the broker after changing logging options. connection_messages true produces the connect and disconnect lines that most of this guide relies on, and a readable timestamp format saves converting Unix seconds by hand. You can generate a complete configuration with the mosquitto.conf generator.

For EMQX 5, raise the console level for a container with the environment variable EMQX_LOG__CONSOLE__LEVEL=debug, or change it at runtime:

EMQX: change the log level at runtime
emqx ctl log set-level debug
# ...reproduce the problem...
emqx ctl log set-level warning

Reading Mosquitto connect lines

A successful connection produces two lines:

Successful connection
2026-10-17T09:12:01: New connection from 192.168.1.20:52144 on port 1883.
2026-10-17T09:12:01: New client connected from 192.168.1.20:52144 as sensor-01 (p2, c1, k60, u'alice').

The bracket at the end describes the CONNECT packet:

FieldMeaning
p2Protocol: p2 = MQTT 3.1.1, p5 = MQTT 5 (p1 would be the old 3.1)
c1Clean session / clean start: c1 = new session, c0 = resume persistent session
k60Keep-alive interval in seconds
u'alice'Username sent by the client (absent if none)

These four fields answer many questions at once: is the client really using the protocol version you think, is it asking for a persistent session, and what keep-alive did it actually set?

Disconnect lines and what they mean

The line Mosquitto prints when a client leaves is the most useful one in the whole log:

Log lineCauseFix
Client sensor-01 disconnected.Clean DISCONNECT from the clientNothing — intended
Client sensor-01 closed its connection.TCP closed without DISCONNECTClient crashed, was killed or lost power
Client sensor-01 has exceeded timeout, disconnecting.No packet for 1.5 × keep-aliveFix the client loop, sleep logic or NAT timeout — see keep-alive timeout
Client sensor-01 already connected, closing old connection.A second client used the same client IDUnique IDs per device — see duplicate client ID
Client sensor-01 disconnected, not authorised.Wrong credentials or no permission to connectCheck username, password and auth plugin
Client sensor-01 disconnected due to protocol error.Malformed or unexpected packetLibrary bug, wrong protocol version, or plain MQTT on a WebSocket listener
Socket error on client sensor-01, disconnecting.Connection broke at the network levelNetwork drop, TLS error or RST from a middlebox
Outgoing messages are being dropped for client sensor-01.Client's queue is fullSlow consumer — raise limits or consume faster

When two lines with already connected, closing old connection alternate every few seconds, two devices are fighting over one client ID and reconnecting in a loop. Generate unique IDs with the client ID generator.

Following one session at debug level

With log_type debug, Mosquitto logs every packet. A subscriber and a publisher on a test topic look roughly like this (Mosquitto 2.x, abbreviated):

Debug log of one publish
Received SUBSCRIBE from sub-01
    testmqtt/logs/# (QoS 1)
sub-01 1 testmqtt/logs/#
Sending SUBACK to sub-01
Received PUBLISH from pub-01 (d0, q1, r0, m1, 'testmqtt/logs/a', ... (5 bytes))
Sending PUBACK to pub-01 (m1, rc0)
Sending PUBLISH to sub-01 (d0, q1, r0, m1, 'testmqtt/logs/a', ... (5 bytes))
Received PUBACK from sub-01 (Mid: 1, RC:0)

The bracket on PUBLISH lines reads: d = DUP flag, q = QoS, r = retain, m = message ID, then topic and payload size. If you see Received PUBLISH but no Sending PUBLISH, the broker accepted the message but had no matching subscription — check the topic filter with the topic matcher. A Denied PUBLISH line means an ACL blocked it; this is the case covered in ACL denied silently.

To reproduce a case quickly, keep the log open in one terminal and drive traffic from another:

Two-terminal debugging
# Terminal 1 — the broker side
docker logs -f mosquitto 2>&1 | grep --line-buffered -E 'sub-01|pub-01'

# Terminal 2 — the client side, with client debug output
mosquitto_sub -d -h localhost -i sub-01 -t 'testmqtt/logs/#' -q 1 &
mosquitto_pub -d -h localhost -i pub-01 -t 'testmqtt/logs/a' -q 1 -m 'hello'

-d makes the clients print their own packet log (Client sub-01 sending CONNECT, received CONNACK (0)), so you can compare both views line by line.

Tracing one client in EMQX

On a busy EMQX broker, global debug logging is too noisy. Trace a single client instead:

EMQX: trace one client ID
emqx ctl trace start client sensor-01 /tmp/sensor-01.log
# ...let the client connect, publish and fail...
emqx ctl trace stop client sensor-01

The trace file contains that client's connect, authentication, subscribe, publish and disconnect events, including the reason when the broker closes the connection. The dashboard has a log trace page that does the same and lets you download the result. You can also trace by topic or by client IP when you do not know which client ID is misbehaving.

A few commands cover most log searches:

Useful log searches
# Everything about one client in the last hour
journalctl -u mosquitto --since "1 hour ago" | grep -F 'sensor-01'

# Which clients time out most often?
grep -o 'Client [^ ]* has exceeded timeout' /var/log/mosquitto/mosquitto.log | sort | uniq -c | sort -rn

# Convert a Unix timestamp from a default Mosquitto log line
date -d @1792228321        # GNU date; on macOS: date -r 1792228321

Correlate the broker time with the client's own logs. If clocks differ, line up on an event both sides see, such as the CONNACK.

When there is no broker log

Without access to the broker's logs, build the same picture from the client side:

  • Log every reason code your library reports and look it up in MQTT reason codes explained or the reason code tool.
  • Subscribe to the broker's $SYS topics if they are exposed — connected-client counts and dropped-message counters often reveal the same trend as the log. See monitoring with $SYS topics.
  • Reproduce the problem with the online MQTT client against the same broker. If it works there, the cause is in the device's code or network, not in the broker.

Confirm the fix in the log

A log line is also your acceptance test. After a change, reproduce the original scenario and watch the log: the has exceeded timeout or already connected line should no longer appear, and the New client connected line should show the parameters you intended (protocol, clean flag, keep-alive). Then turn debug logging off again.

The broker sees every client's mistakes, but only writes them down if you ask. Configure connection messages and a readable timestamp, raise the level only while you investigate, and learn the handful of disconnect lines — they map almost one to one to the fix.

Frequently asked questions

How do I enable debug logging in Mosquitto?

Add log_type debug (or log_type all) to mosquitto.conf together with a log_dest such as stdout or file, then restart the broker. Debug logging prints every packet the broker receives and sends, so switch it off again once you have found the problem.

What does 'has exceeded timeout, disconnecting' mean in Mosquitto?

The broker received nothing from the client for 1.5 times its keep-alive interval, so it closed the connection. The client's network loop stopped, the device slept, or a NAT gateway dropped the connection. Check the keep-alive value in the 'New client connected' line (k60 means 60 seconds).

How can I see logs for a single client in EMQX?

Use a log trace: emqx ctl trace start client <clientid> <file> records everything for that client ID into a file, and the EMQX dashboard offers the same in its log trace page. Stop it with emqx ctl trace stop client <clientid> when you are done.