Back to Blog
IoT Engineering

MQTT QoS 0, 1, and 2 Explained: Choosing the Right Level for IoT

Most IoT developers default to QoS 1 for everything and wonder why their battery life suffers and their broker is generating thousands of duplicate messages. Understanding the protocol mechanics behind each QoS level makes the right choice obvious.

May 6, 2024
10 min read
MQTTQoSIoT ProtocolsESP32

MQTT QoS 0, 1, and 2 Explained: Choosing the Right Level for IoT

Quality of Service in MQTT is one of those concepts that every IoT developer knows exists and almost nobody fully understands until they hit a production problem. This guide explains the protocol mechanics, not just the labels — so you can make the right choice for each message type in your system.

What QoS Actually Controls

MQTT QoS governs the delivery guarantee between a *publisher* and the *broker*, and separately between the *broker* and each *subscriber*. It does NOT provide end-to-end delivery guarantees — QoS 1 from device to broker doesn't help you if the broker drops the message before delivering to the subscriber.

The three levels trade off protocol overhead (extra round-trips, stored message state) for delivery assurance.

QoS 0: Fire-and-Forget

The simplest path: publisher sends one PUBLISH packet, no acknowledgement, no retransmission. If the network drops the packet, it's gone.

Publisher → [PUBLISH] → Broker → [PUBLISH] → Subscriber
                                              (delivered or lost, no feedback)

Protocol overhead: 2-byte fixed header + variable header. Zero state stored at broker or client.

Battery impact: Minimum possible — one radio transmission per message.

// ESP32: QoS 0 publish (PubSubClient default)
void publishTelemetry(float temperature) {
  char payload[64];
  snprintf(payload, sizeof(payload),
    "{"temp":%.2f,"ts":%lu}", temperature, millis());

// Third argument: retain flag. Fourth: QoS (0 = fire-and-forget) mqttClient.publish("devices/" DEVICE_ID "/telemetry", payload, false); // PubSubClient defaults to QoS 0 }

Use QoS 0 for:

  • High-frequency sensor telemetry (temperature, humidity, GPS position updates)
  • Data where occasional loss is acceptable and the next reading will arrive soon
  • Battery-powered devices where every extra packet matters
  • Video/audio stream metadata where stale data is worse than missing data
  • Do NOT use QoS 0 for:

  • Actuator commands ("open valve", "trigger alarm")
  • Configuration updates
  • Any message where loss causes a dangerous or unrecoverable system state
  • QoS 1: At-Least-Once Delivery

    The publisher stores the message and transmits a PUBLISH packet. The broker must respond with PUBACK. If PUBACK isn't received within the timeout, the publisher retransmits (with DUP flag set) until acknowledged.

    Publisher → [PUBLISH packetId=42]  → Broker
    Publisher ← [PUBACK  packetId=42]  ← Broker
    (Broker delivers to subscribers, which may also PUBACK independently)
    

    Protocol overhead: One extra round-trip per message. Broker stores message in session until all subscribers acknowledge.

    The duplicate problem: If the broker receives the PUBLISH and sends PUBACK, but PUBACK is lost in transit, the publisher retransmits. The broker may deliver the message twice to subscribers. Your subscriber code must handle duplicates:

    // Node.js: idempotent QoS 1 subscriber with deduplication
    const seen = new Map() // messageId → timestamp
    const DEDUP_WINDOW_MS = 30000

    mqttClient.on('message', (topic, buffer, packet) => { const messageId = ${packet.messageId}-${topic}

    // Deduplicate if (seen.has(messageId)) return seen.set(messageId, Date.now())

    // Clean up dedup window periodically if (seen.size > 10000) { const cutoff = Date.now() - DEDUP_WINDOW_MS seen.forEach((ts, id) => { if (ts < cutoff) seen.delete(id) }) }

    processMessage(topic, JSON.parse(buffer)) })

    Use QoS 1 for:

  • Actuator commands (fire once, dedup on the receiver)
  • Configuration pushes to devices
  • Billing-adjacent telemetry where loss is unacceptable but duplicates are manageable
  • Alert notifications that must be delivered
  • QoS 2: Exactly-Once Delivery

    The most complex level — a four-message handshake ensures the message is delivered exactly once, with no duplicates.

    Publisher → [PUBLISH  packetId=42] → Broker   (store message)
    Publisher ← [PUBREC   packetId=42] ← Broker   (received and stored)
    Publisher → [PUBREL   packetId=42] → Broker   (release: safe to deliver)
    Publisher ← [PUBCOMP  packetId=42] ← Broker   (complete: discard stored msg)
    (Broker now delivers to subscribers with QoS 2 handshake)
    

    Protocol overhead: Four round-trips per message. Both publisher and broker must persist state across potential connection drops. Subscriber must also maintain state for the second handshake.

    Battery impact: Significantly higher — four radio transmissions plus state writes to flash (if persisting across reboots).

    Throughput impact: On AWS IoT Core, QoS 2 message rate is subject to tighter throttle limits than QoS 0/1. At scale, QoS 2 can become a bottleneck.

    // Node.js: publish at QoS 2 for billing data
    client.publish(
      devices/${deviceId}/billing/consumption,
      JSON.stringify({ kWh: reading.kWh, periodEnd: reading.ts }),
      { qos: 2 },
      (err) => {
        if (err) {
          // QoS 2 publish failed after all retries — escalate
          alertOpsTeam(Billing message undelivered for ${deviceId})
        }
      }
    )
    

    Use QoS 2 for:

  • Financial/billing records (power consumption, water metering, gas metering)
  • Inventory tracking where double-counting has monetary consequences
  • Regulatory reporting where audit logs require exactly-once semantics
  • Cases where your business cannot tolerate duplicates AND cannot deduplicate application-side
  • Avoid QoS 2 for:

  • Anything where throughput > 100 msg/sec per connection
  • Battery-powered devices (the 4-message handshake drains batteries)
  • Most telemetry — QoS 1 + application dedup is cheaper and achieves the same result
  • Overhead Comparison Table

    | QoS | Messages per publish | Bytes overhead | State at broker | State at client | |-----|---------------------|----------------|-----------------|-----------------| | 0 | 1 | ~2 bytes | None | None | | 1 | 2 (+ retries if needed) | ~4 bytes × 2 | Until PUBACK | Until PUBACK | | 2 | 4 | ~4 bytes × 4 | Until PUBCOMP | Until PUBCOMP |

    At 1,000 messages/second, QoS 2 generates 4,000 broker-level operations versus 1,000 for QoS 0. The broker cost difference is real: AWS IoT Core charges per message, and a four-message QoS 2 exchange counts as four messages.

    Persistent Sessions and QoS Interaction

    QoS 1 and 2 only queue messages for offline subscribers if you use persistent sessions (cleanSession: false on the MQTT connection). With a clean session (the default), the broker discards QoS 1+ messages for any disconnected subscriber.

    For device command delivery, always use persistent sessions:

    // Device connects with persistent session
    const client = mqtt.connect(brokerUrl, {
      clientId: deviceId,
      clean: false,          // persistent session
      reconnectPeriod: 5000,
    })

    // Commands queued while device is offline will be delivered on reconnect client.subscribe(devices/${deviceId}/commands, { qos: 1 })

    Decision Matrix

    | Message Type | Recommended QoS | Reason | |-------------|----------------|--------| | Sensor telemetry (frequent) | 0 | Loss acceptable, next reading coming | | Sensor telemetry (infrequent, >5min) | 1 | Loss would create data gaps | | Actuator command | 1 + dedup | Must arrive, duplicates manageable | | Configuration update | 1 | Must arrive, idempotent | | Billing / metering | 2 | Exactly-once required, low rate | | OTA firmware chunk | 1 | HTTP OTA is better; use 1 if MQTT-based | | Heartbeat / keepalive | 0 | Loss is fine — broker has LWT |

    For the broader MQTT vs HTTP decision, see [MQTT vs HTTP for IoT](/blog/mqtt-vs-http-iot-protocol-comparison). For how QoS interacts with fleet-scale message routing on AWS, see [IoT Fleet Management with AWS IoT Core](/blog/iot-fleet-management-aws-iot-core).

    Need help with IoT protocol design? [Contact Code Caracal](/contact) — we've shipped these systems for clients across 15+ countries.

    Written by CodeCaracal Engineering

    We write from production experience — every technique in our articles has been deployed to real clients. No academic theory.

    More Articles

    Business · 12 min read

    IoT Device Compliance: FCC, CE, and Product Certification Guide for Hardware Startups

    Business · 11 min read

    What to Look for When Hiring an IoT Development Partner: 8 Critical Criteria

    Business · 11 min read

    IoT MVP to Production: Realistic Timeline and Budget for Hardware Startups

    Business · 11 min read

    IoT Development Agency vs Building In-House: A Decision Framework for Founders

    IoT Dashboard · 13 min read

    Next.js IoT Analytics Dashboard: From Sensor Data to Production App

    Business · 11 min read

    How Much Does It Cost to Build an IoT Product in 2024? A Realistic Breakdown

    IoT Dashboard · 11 min read

    IoT Dashboard UX: Design Principles for Industrial Monitoring Interfaces

    IoT Dashboard · 12 min read

    Node.js WebSocket Server: The Real-Time Backend for IoT Dashboards

    Cloud & DevOps · 12 min read

    Containerizing IoT Backend Services with Docker: From Dev to Production

    IoT Dashboard · 14 min read

    Grafana + InfluxDB IoT Monitoring: Complete Production Setup Guide

    IoT Dashboard · 12 min read

    Building Real-Time IoT Dashboards with React and Recharts

    Cloud & DevOps · 13 min read

    CI/CD for Embedded Firmware: Automated Build, Test, and OTA Release Pipeline

    Mobile Development · 12 min read

    Flutter Offline-First IoT Apps: Hive + Sync Architecture That Works in the Field

    Cloud & DevOps · 14 min read

    Terraform for IoT Infrastructure: Provisioning AWS IoT Core, Lambda, and InfluxDB as Code

    Mobile Development · 10 min read

    Flutter IoT Alerts: Firebase Push Notifications for Device Events

    Cloud & DevOps · 12 min read

    Deploying IoT Backends on AWS: ECS Fargate vs Lambda vs EC2 Decision Guide

    Mobile Development · 11 min read

    Flutter + MQTT: Building Production IoT Mobile Apps That Scale

    Mobile Development · 13 min read

    Flutter BLE: Building a Bluetooth IoT Controller App from Scratch

    Cloud & DevOps · 13 min read

    AWS IoT Core vs Azure IoT Hub vs Google Cloud IoT: 2024 Honest Comparison

    IoT Engineering · 13 min read

    Kafka vs RabbitMQ for IoT: Choosing the Right Message Queue for High-Volume Telemetry

    IoT Engineering · 14 min read

    IoT System Testing: Unit, Integration, Hardware-in-the-Loop, and End-to-End

    IoT Engineering · 14 min read

    Predictive Maintenance with IoT Sensor Data: From Threshold to Machine Learning

    Embedded Systems · 14 min read

    IoT Bootloader Design: Secure Boot, A/B Partitions, and Reliable OTA Recovery

    IoT Engineering · 14 min read

    Multi-Tenant IoT Platform Architecture: Isolation, Scaling, and Data Partitioning

    Embedded Systems · 14 min read

    Memory Management in Embedded Firmware: Avoiding Heap Fragmentation and Stack Overflows

    IoT Engineering · 13 min read

    IoT Cost Optimization: How We Cut AWS IoT Bills by 60% Without Sacrificing Reliability

    IoT Engineering · 12 min read

    Edge Computing in IoT: When to Process On-Device vs In the Cloud

    IoT Engineering · 13 min read

    Digital Twins for IoT: Building a Virtual Mirror of Your Physical Devices

    Embedded Systems · 14 min read

    ESP32 Deep Sleep Mastery: Cutting Power Consumption from 240mA to 10µA

    IoT Engineering · 14 min read

    IoT Monitoring and Observability: Metrics, Logs, and Distributed Tracing

    Embedded Systems · 14 min read

    Debugging Embedded Firmware: JTAG, GDB, Logic Analyzers, and Serial Tracing

    IoT Engineering · 12 min read

    WebSocket vs MQTT vs Server-Sent Events: Real-Time IoT Protocol Deep Dive

    Embedded Systems · 13 min read

    STM32 HAL vs Low-Level Drivers: When the Abstraction Costs You Too Much

    IoT Engineering · 13 min read

    IoT Data Pipeline: From Raw Sensor Reading to Live Dashboard in Under 100ms

    IoT Engineering · 13 min read

    Zero-Touch IoT Device Provisioning: Scaling from 10 to 100,000 Devices

    Embedded Systems · 13 min read

    UART vs SPI vs I2C: Choosing the Right Protocol for Sensor Integration

    IoT Engineering · 12 min read

    Real-Time IoT Alerting: From Simple Thresholds to ML Anomaly Detection

    Embedded Systems · 12 min read

    ESP32 Partition Table: Designing Flash Layout for Production Firmware

    IoT Engineering · 12 min read

    IoT Architecture Patterns: Hub-and-Spoke, Mesh, and Edge-Cloud Hybrid

    Embedded Systems · 13 min read

    IoT Battery Life Optimization: Engineering Devices That Last Years on a Single Charge

    IoT Engineering · 13 min read

    Time-Series Databases for IoT: InfluxDB vs TimescaleDB vs AWS Timestream

    Security · 14 min read

    Zero-Trust Security for Embedded IoT: Why Your Devices Are Probably Vulnerable

    Embedded Systems · 14 min read

    FreeRTOS on ESP32: Task Scheduling, Queues, and Resource Management for IoT

    IoT Engineering · 12 min read

    Building a Production IoT Gateway with Raspberry Pi and Node.js

    Embedded Systems · 13 min read

    ESP32 vs STM32: Choosing the Right Microcontroller for Your IoT Project

    Mobile Development · 10 min read

    Flutter + WebSocket: Building Real-Time IoT Dashboards That Don't Stutter

    IoT Engineering · 13 min read

    IoT Fleet Management at Scale: AWS IoT Core Device Registry and Provisioning

    IoT Engineering · 11 min read

    MQTT vs HTTP for IoT: Which Protocol Wins in Production?

    IoT Engineering · 12 min read

    ESP32 → MQTT → AWS IoT Core: The Production-Grade Architecture Guide

    Let's Build Together

    Got an IoT challenge?
    We've shipped it.

    Whether you need a fleet to track, a factory to monitor, or a farm to automate — our team has done it before and we'd love to build it with you. Typical response time: under 24 hours.

    No upfront commitment99.9% uptime SLANDA on requestFixed-price options