Skip to content

How to Fix MQTT QoS Errors

DodaTech Updated 2026-06-24 4 min read

In this tutorial, you'll learn about How to Fix MQTT QoS Errors. We cover key concepts, practical examples, and best practices.

The Problem

Your MQTT messages are not reliably delivered despite setting QoS 2, or you receive the same message multiple times. The broker log shows Too many PUBREL packets or MQTT protocol violation. QoS configuration errors undermine the reliability of your IoT messaging.

Quick Fix

Fix 1: Choosing the Wrong QoS Level

WRONG — using QoS 0 for critical commands:

client.publish("actuator/valve", "open", qos=0)
# (message may be lost if the connection drops)

RIGHT — match QoS to use case:

# QoS 0 (At most once) — for non-critical sensor data
client.publish("sensors/temp", "23.5", qos=0)
# (fire and forget — fastest, no guarantee)

# QoS 1 (At least once) — for most commands
client.publish("actuator/valve", "open", qos=1)
# (guaranteed delivery, may duplicate)

# QoS 2 (Exactly once) — for financial or critical commands
client.publish("payment/authorize", "txn123", qos=2)
# (guaranteed delivery, no duplicates — but 2x round trips)

Fix 2: Duplicate Messages with QoS 1

WRONG — not handling duplicate message IDs:

def on_message(client, userdata, msg):
    print(f"Received: {msg.payload}")
    # (duplicate messages are processed again — may cause double actions)

RIGHT — deduplicate with message IDs:

processed_ids = set()

def on_message(client, userdata, msg):
    # Check for duplicate using message ID
    msg_id = f"{msg.topic}:{msg.payload}"
    if msg_id in processed_ids:
        return  # skip duplicate
    processed_ids.add(msg_id)
    print(f"Received: {msg.payload}")

Fix 3: QoS 2 Protocol Issues

WRONG — broker runs out of memory for pending QoS 2 packets:

# Mosquitto log: "Too many PUBREL packets"
# (publisher sends many QoS 2 messages without waiting for completion)

RIGHT — throttle QoS 2 publishing:

import time

def publish_qos2(client, topic, payload):
    # Wait for previous QoS 2 message to complete
    while client._out_pending_qos2_count > 5:
        time.sleep(0.1)
    info = client.publish(topic, payload, qos=2)
    info.wait_for_publish()  # blocks until PUBCOMP received

Fix 4: Session State Conflicts

WRONG — using clean session=True on reconnect:

client.connect("broker", 1883, 60, clean_session=True)
# (previous QoS 1/2 messages are discarded — lost messages)

RIGHT — use persistent sessions for QoS reliability:

client.connect("broker", 1883, 60, clean_session=False)
# (broker stores pending QoS 1/2 messages for this client)

Fix 5: Client ID and Session Mismatch

client1 = mqtt.Client("sensor1")
client1.connect("broker", 1883, 60, clean_session=False)
# ... messages are queued ...

client2 = mqtt.Client("sensor1")  # same ID
client2.connect("broker", 1883, 60, clean_session=True)
# (clean session=True clears the queued messages for client1!)

RIGHT — use unique client IDs or consistent clean_session:

# Each device gets a unique, persistent client ID
client = mqtt.Client(client_id="sensor1_kitchen")
client.connect("broker", 1883, 60, clean_session=False)

Fix 6: Verifying QoS with Mosquitto Test

# Terminal 1 (subscribe with QoS 2):
mosquitto_sub -h localhost -t test -q 2 -v

# Terminal 2 (publish with QoS 2):
mosquitto_pub -h localhost -t test -m "hello" -q 2

# Terminal 1 output:
# test hello
# (if QoS 2 works end-to-end, no duplicates appear)

Use DodaTech's MQTT Reliability Analyzer to measure message delivery rates, detect duplicate messages, and optimize QoS configuration for your IoT deployment.

Prevention

  • Use QoS 2 only when exactly-once delivery is required.
  • Implement deduplication on subscribers for QoS 1.
  • Throttle QoS 2 publishing to avoid broker buffer overflow.
  • Use clean_session=False with persistent client IDs.
  • Monitor broker logs for PUBREL flooding.

Common Mistakes with qos error

  1. Forgetting deriving (Show, Eq) on custom data types needed for debugging
  2. Placing the wildcard pattern first in case expressions, making all subsequent patterns unreachable
  3. Using head and tail instead of pattern matching, causing runtime errors on empty lists

These mistakes appear frequently in real-world MQTT code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### Does QoS 2 guarantee exactly-once delivery?

Yes, the MQTT protocol guarantees exactly-once delivery for QoS 2 through a four-packet handshake (PUBLISH → PUBREC → PUBREL → PUBCOMP). However, if the client crashes after receiving the message but before acknowledging, the message is re-sent on reconnect.

What is the performance impact of QoS 2?

QoS 2 requires 2 round trips per message versus 0 for QoS 0 and 1 for QoS 1. Throughput drops significantly with QoS 2 — expect 30-50% of the messages per second compared to QoS 0.

Can a publisher and subscriber use different QoS levels?

Yes, the publisher specifies the QoS for outgoing messages and the subscriber specifies the QoS for incoming messages. The broker delivers at the lower of the two QoS levels. A QoS 2 publish to a QoS 0 subscriber is delivered as QoS 0.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro