Quality of Service, almost always written QoS, is the agreement between the sender of a message and its receiver about the guarantee of delivery for that specific message. It is one of MQTT’s defining features and the single biggest reason the protocol works well over networks that are slow, congested, or unreliable. Rather than forcing every application to build its own retry-and-acknowledge logic, MQTT bakes three well-defined delivery guarantees into the protocol itself, and lets each message and each subscription choose the one that fits.
This article explains all three QoS levels in detail: what each guarantees, the exact packet exchanges that implement them, the often-misunderstood fact that QoS is negotiated separately on each hop, the QoS downgrade behavior, the role of the packet identifier, and how to choose the right level for a given use case. It is a technical reference; related mechanisms such as the publish/subscribe operations, persistent sessions, and the packet structure each have their own dedicated articles in this category.
Table of Contents
QoS levels at a glance
| Level | Name | Guarantee | Acknowledgement | Duplicates possible? |
|---|---|---|---|---|
| 0 | At most once | Best effort, no guarantee | None | No protocol retransmission duplicates |
| 1 | At least once | Delivered at least once | PUBACK | Yes |
| 2 | Exactly once | Delivered exactly once (protocol level) | PUBREC → PUBREL → PUBCOMP | No protocol retransmission duplicates |
The two hops of every delivery
Before looking at the levels themselves, there is a concept that governs all of them and that causes more confusion than anything else in QoS: a message’s journey from publisher to subscriber is not one delivery but two, and QoS applies to each independently.
When you talk about QoS in MQTT, you have to consider both sides of message delivery separately:
- Message delivery from the publishing client to the broker.
- Message delivery from the broker to the subscribing client.
These two hops are distinct, and their QoS levels can differ. The client that publishes a message defines the QoS level of the message when it sends the PUBLISH to the broker; that QoS governs the first hop. The broker then delivers the message to each subscribing client using the QoS level that each subscriber requested when it subscribed; that QoS governs the second hop, and it can be different for every subscriber.
This separation is why a publisher cannot simply “send a message at QoS 2 and know every subscriber gets it exactly once.” The publisher controls only the first hop. What happens on the second hop depends on how each subscriber subscribed. The interaction between the two is the QoS downgrade behavior, covered below.
Why QoS matters
QoS is a key feature of the MQTT protocol because it gives each client the power to choose a level of service that matches its network reliability and its application logic. A client on a rock-solid wired link sending non-critical data can choose the cheapest level; a client on a flaky cellular link sending data that must not be lost can choose a stronger guarantee.
Crucially, MQTT manages the retransmission of messages and provides delivery guarantees even when the underlying transport is not reliable. TCP gives MQTT an ordered, error-checked byte stream while a connection is healthy, but connections drop. When a TCP connection drops, its state disappears entirely; MQTT session state, by contrast, can survive. MQTT QoS 1 and 2 can preserve delivery state across MQTT session reconnections, which ordinary TCP connections cannot, because the guarantee lives in the MQTT session rather than in the transport. This is what makes QoS so valuable: it makes reasoning about delivery in unreliable networks tractable, by turning “did this arrive?” into a defined protocol contract rather than an application-level guess.
How each level works
Let us look at exactly how each QoS level is implemented on the wire, because the packet exchanges are what give each level its guarantee.
QoS 0 — at most once
QoS 0 is the minimal level. It guarantees best-effort delivery, which is to say it guarantees nothing beyond what the transport itself provides. The recipient does not acknowledge receipt, and the sender does not store the message or retransmit it. The message is sent once, and whatever happens, happens.
QoS 0 is often called “fire and forget,” and it provides the same guarantee as the underlying TCP protocol: if the connection is healthy when the message is sent, it will very likely arrive, but if the connection drops at the wrong moment, the message is simply lost with no recovery. The exchange is a single packet:
Sender --- PUBLISH (QoS 0) --> Receiver
There is no acknowledgement and no packet identifier, because there is nothing to match an acknowledgement against. This makes QoS 0 the lightest and fastest level, with the least overhead, at the cost of any delivery guarantee.
QoS 1 — at least once
QoS 1 guarantees that a message is delivered at least one time to the receiver. The mechanism is a single round trip with stored state. The sender stores the message until it receives a PUBACK packet from the receiver acknowledging receipt:
Sender --- PUBLISH (QoS 1, packet id) --> Receiver
Sender <-- PUBACK (same packet id) --- Receiver
The sender uses the packet identifier in each packet to match the PUBLISH to its corresponding PUBACK. If the sender does not receive a PUBACK, it resends the PUBLISH packet. The MQTT specification does not define the retransmission timing; how long the sender waits before resending is implementation-specific. When a receiver gets a message with QoS 1, it can process it immediately; if the receiver is a broker, it sends the message on to all subscribing clients and then replies with a PUBACK.
Because a resend can occur after the original PUBLISH was actually received (for example, if the PUBACK was lost on its way back), the same message can be delivered more than once. This is the defining characteristic of QoS 1: delivery is guaranteed, but duplicates are possible, which is why it is “at least once.” Any application using QoS 1 must be able to tolerate receiving the same message more than once.
When the publishing client resends, it sets the DUP (duplicate) flag on the PUBLISH. At QoS 1 this flag is used only for internal purposes and does not change whether the receiver acknowledges; the receiver sends a PUBACK regardless of whether the DUP flag is set. The DUP flag signals that the PUBLISH might be a retransmission attempt; the receiver may use it contextually during retransmission handling, but it does not by itself alter processing.
QoS 2 — exactly once
QoS 2 is the highest level of service in MQTT. It guarantees that each message is received exactly once by the intended recipient, between MQTT protocol peers. It is the safest and the slowest level, because the guarantee is provided by at least two request/response flows, a four-part handshake, between the sender and the receiver. The sender and receiver use the packet identifier of the original PUBLISH throughout to coordinate the exchange:
Sender --- PUBLISH (QoS 2, packet id) --> Receiver
Sender <-- PUBREC (same packet id) --- Receiver
Sender --- PUBREL (same packet id) --> Receiver
Sender <-- PUBCOMP (same packet id) --- Receiver
Walking through the four parts:
- The sender sends the PUBLISH at QoS 2.
- When the receiver gets the QoS 2 PUBLISH, it stores the message state and replies with a PUBREC packet, acknowledging the PUBLISH. If the sender does not receive a PUBREC, it resends the PUBLISH with the DUP flag set, repeating until it receives an acknowledgement.
- Once the sender receives the PUBREC, it can safely discard the initial PUBLISH packet. It stores the QoS 2 transaction state and responds with a PUBREL packet.
- After the receiver gets the PUBREL, it completes delivery processing of the message, then discards its stored state and answers with a PUBCOMP packet. The same release of state is true when the sender receives the PUBCOMP.
This ordering matters: the receiver acknowledges receipt with PUBREC on the first hop, but a strict implementation defers actually completing delivery (the onward delivery or application processing of the message) until it receives the PUBREL. Separating receipt from completion is what allows a duplicate PUBLISH, arriving before PUBREL because a PUBREC was lost, to be recognized and discarded without the message being delivered twice. Note that implementations vary in exactly when they perform application-level processing, but the protocol’s design intent is this separation of receipt and completion.
The key to the exactly-once guarantee is what the receiver stores and when. Until the receiver completes processing and sends the PUBCOMP back to the sender, it keeps a reference to the packet identifier of the original PUBLISH. That stored reference is what lets it recognize, and discard, a duplicate PUBLISH that arrives because an acknowledgement was lost, which is precisely how processing the message a second time is avoided. Once the sender receives the PUBCOMP, the packet identifier of the published message becomes available for reuse.
When the QoS 2 flow is complete, both parties are certain the message was delivered, and the sender has confirmation of delivery. If a packet is lost along the way, the sender is responsible for retransmitting the message within an implementation-defined interval. This is equally true whether the sender is an MQTT client or the broker, and the recipient is responsible for responding to each command message accordingly.
One important nuance: the exactly-once guarantee holds between MQTT protocol peers, at the protocol level. It does not automatically extend to your whole application end to end. Application-level duplicates can still arise from session persistence behavior, client implementation bugs, the behavior of bridges between brokers, or backend processing logic. QoS 2 guarantees that the MQTT exchange delivers the message exactly once; it does not guarantee that everything downstream of that exchange also processes it exactly once. For truly idempotent end-to-end behavior, design your application accordingly rather than relying on QoS 2 alone.
QoS downgrade: why your subscriber’s level wins
Because QoS applies separately to the two hops, the level a subscriber receives at can be lower than the level the publisher used. This is called a QoS downgrade, and understanding it prevents a common class of surprises.
Recall that the publishing client defines the QoS when it sends the PUBLISH to the broker, but the broker delivers to each subscriber using the QoS that subscriber defined during subscription. When the subscriber’s requested QoS is lower than the publisher’s, the broker delivers the message at the lower level.
A concrete example. Client A is the sender; client B is the receiver. Client B subscribes to the broker with QoS 1. Client A then sends a message to the broker with QoS 2. The broker delivers the message to client B at QoS 1, the lower of the two. As a result, the message can be delivered more than once to client B, because QoS 1 guarantees delivery at least once and does not prevent duplicate deliveries. Client A used QoS 2 and saw an exactly-once exchange on its hop, but that did nothing to give client B exactly-once delivery, because client B subscribed at QoS 1.
The practical lesson is that the end-to-end guarantee a subscriber experiences is determined by the minimum of the two hops’ QoS levels. If you need a subscriber to get exactly-once delivery, both the publisher must publish at QoS 2 and that subscriber must subscribe at QoS 2. Setting one without the other does not get you there.
The same minimum rule applies to retained messages. When the broker stores a retained message, it stores the original publish QoS alongside it. When that retained message is later delivered to a new subscriber, the subscriber still receives it at the minimum of the stored publish QoS and its own subscription QoS, exactly as a live message would be downgraded. The retained flag changes when the message is delivered, not how the two-hop QoS rule applies to it.
The packet identifier
Threaded through QoS 1 and QoS 2 is the packet identifier, and it is worth pulling together what it does, because it is the mechanism that makes acknowledgement matching possible.
The packet identifier that MQTT uses for QoS 1 and QoS 2 is a two-byte value that is unique between a specific client and the broker, within a single interaction. It is what lets the sender match a PUBLISH to its PUBACK, or a PUBLISH to its PUBREC, PUBREL, and PUBCOMP. It identifies the in-flight protocol transaction, not the application message itself; the same application payload sent again later may travel under a different packet identifier.
Two properties matter in practice. First, the identifier is not unique across all clients; it is scoped to one client-broker interaction. Two different clients can each be using packet identifier 1234 at the same time with no conflict, because the broker tracks them separately per connection. Second, once a flow completes, the packet identifier becomes available for reuse. This reuse is the reason the packet identifier does not need to exceed 65,535: it is unrealistic for a single client to have more than that many interactions in flight simultaneously without completing any. Application developers generally do not manage packet identifiers themselves; the client library or the broker sets and tracks them as an internal detail.
It is worth being clear about the scope of the QoS guarantee itself, too: it applies per client session, between a client and the broker, not globally across an entire system. Each of the two hops is its own QoS interaction with its own packet identifiers and stored state. The guarantee is a contract on those individual MQTT exchanges, which is why the end-to-end behavior a message experiences is the composition of the per-hop guarantees rather than a single system-wide property.
What happens when a packet is lost
The differences between the levels are clearest when something goes wrong, because that is exactly what QoS exists to handle. Consider the same message sent at each level, with a packet lost in transit.
At QoS 0, if the PUBLISH is lost, nothing happens to recover it. There is no acknowledgement the sender is waiting for, so it never learns the message did not arrive, and it never resends. The message is simply gone. This is the trade-off you accept for zero overhead: on a healthy link the loss rate is low, but there is no safety net.
At QoS 1, loss is detected and recovered through the missing acknowledgement. If the PUBLISH is lost, no PUBACK comes back, so after its implementation-defined wait the sender resends the PUBLISH with the DUP flag set, and keeps doing so until a PUBACK arrives. If instead the PUBLISH arrived but the PUBACK was lost on the return trip, the sender still sees no acknowledgement and resends, so the receiver gets the message a second time. This is exactly why QoS 1 produces duplicates: the sender cannot distinguish “the message was lost” from “the acknowledgement was lost,” so it errs on the side of resending, which guarantees at-least-once delivery at the cost of possible repeats.
At QoS 2, loss is recovered without producing a duplicate, which is the whole point of the extra handshake. If the PUBLISH is lost, no PUBREC comes back and the sender resends the PUBLISH with the DUP flag. If the PUBLISH arrived but its PUBREC was lost, the sender resends the PUBLISH; the receiver, which is still holding the stored packet-identifier reference from the first PUBLISH, recognizes the resend as a duplicate, does not process it again, and simply re-sends the PUBREC. The same logic protects the PUBREL and PUBCOMP steps. At every stage, the stored state on each side lets a lost acknowledgement be recovered by retransmission without the message being acted on twice. That is how QoS 2 turns the unavoidable ambiguity that causes QoS 1 duplicates into a recoverable, de-duplicated exchange.
The cost of each level
Choosing a QoS level is fundamentally a cost decision, so it helps to be concrete about what each level costs.
The clearest cost is round trips. QoS 0 is a single packet with no return. QoS 1 is one round trip: a PUBLISH and a PUBACK. QoS 2 is two round trips: PUBLISH/PUBREC followed by PUBREL/PUBCOMP. More round trips mean more latency before a message is fully confirmed, and on a high-latency link such as satellite the difference between one and two round trips is significant.
The second cost is stored state. QoS 0 stores nothing. QoS 1 requires the sender to store the message until it is acknowledged. QoS 2 requires both sides to store state for the duration of the handshake, the sender holding the message and then the PUBREC, the receiver holding the packet-identifier reference until PUBCOMP. On a broker handling very large numbers of clients, this per-message state is a real resource consideration, which is part of why QoS 2 is used sparingly.
The third cost is bandwidth and processing. Every acknowledgement is a packet on the wire and work for both endpoints. QoS 0 adds none, QoS 1 adds one acknowledgement, and QoS 2 adds three. For a constrained device sending frequent small readings, multiplying every reading by four packets is rarely worth it unless the data genuinely demands exactly-once handling.
These costs are why QoS is a per-message and per-subscription choice rather than a single global setting. You spend the overhead where the guarantee is worth it and save it everywhere else.
Choosing the right QoS level
The QoS that is right for you depends heavily on your use case, and the choice is a deliberate trade-off between delivery guarantee on one side and overhead, latency, and complexity on the other. Here are guidelines for each level.
Use QoS 0 when
- You have a completely or mostly stable connection between sender and receiver. A classic use case for QoS 0 is connecting a test client or a front-end application to a broker over a reliable, wired connection.
- You do not mind if a few messages are lost occasionally. Losing some messages can be acceptable when the data is not that important, or when it is sent at short intervals so the next reading arrives soon anyway.
- You do not need message queuing. Messages are queued for disconnected clients when they were sent at QoS 1 or 2 and the client has a persistent session, so QoS 0 messages are typically not held for an offline client. (The specification permits brokers to queue QoS 0 messages optionally in some cases, but you should not rely on it.)
Use QoS 1 when
- You need to receive every message and your use case can handle duplicates. QoS 1 is the most frequently used service level, because it guarantees the message arrives at least once while allowing for multiple deliveries. Your application must tolerate duplicates and process them accordingly, for example by making the handling idempotent or by de-duplicating on an application-level identifier.
- You cannot bear the overhead of QoS 2. QoS 1 delivers messages much faster than QoS 2 because it uses a single round trip rather than a four-part handshake.
Use QoS 2 when
- It is critical to your application to receive every message exactly once. This is often the case when a duplicate delivery would actively harm application users or subscribing clients, for instance a command that must not be executed twice. Be aware of the overhead, and that the QoS 2 interaction takes more time to complete because of its four-part handshake.
A reasonable default for many systems is QoS 1, reserving QoS 0 for high-frequency, loss-tolerant telemetry and QoS 2 for the comparatively rare messages where a duplicate would be genuinely damaging.
Queuing of QoS 1 and 2 messages
A point that connects QoS to the session model: all messages sent at QoS 1 and 2 are queued for offline clients until the client is available again. However, this queuing is only possible if the client has a persistent session. A client with a clean session, or one that subscribed at QoS 0, will not have messages held for it while it is offline.
This is why QoS and persistent sessions are usually discussed together. The delivery guarantee of QoS 1 and 2 across a disconnection depends on the broker actually storing the undelivered messages, and that storage is a property of the persistent session. The persistent sessions article covers exactly what is stored and under what conditions.
How QoS connects to the rest of MQTT
QoS sits at the center of MQTT’s reliability story and touches many other features:
- The QoS field is set on the PUBLISH packet and on each subscription, as covered in the publish/subscribe operations article.
- The acknowledgement packets (PUBACK, PUBREC, PUBREL, PUBCOMP) are the wire-level implementation of QoS 1 and 2 and are part of MQTT’s packet set.
- Persistent sessions are what allow QoS 1 and 2 messages to be queued for offline clients.
- The packet identifier is shared with the subscribe and unsubscribe flows.
- In MQTT 5, the protocol forbids retransmitting on a healthy TCP connection (it must re-send only after the connection was closed), and the acknowledgement packets gain reason codes; both are covered in the MQTT 5 articles.
Understanding the three levels, the two-hop model, and the downgrade behavior gives you the vocabulary to make every reliability decision in an MQTT deployment deliberately rather than by guesswork.
Frequently asked questions
What are the three MQTT QoS levels?
QoS 0 (at most once) is best-effort with no acknowledgement; QoS 1 (at least once) guarantees delivery but allows duplicates; QoS 2 (exactly once) guarantees delivery exactly once between protocol peers using a four-part handshake. Higher levels give stronger guarantees at the cost of more overhead.
Does QoS 2 really guarantee exactly-once delivery?
At the MQTT protocol level, yes: the four-part handshake ensures the MQTT exchange delivers the message exactly once. However, application-level duplicates can still arise from persistence behavior, client bugs, bridges, or backend processing, so exactly-once at the application layer requires designing for it rather than relying on QoS 2 alone.
Why did my subscriber receive duplicates even though the publisher used QoS 2?
Because QoS is negotiated separately on each hop. If the subscriber subscribed at QoS 1, the broker downgrades delivery to QoS 1 for that subscriber, and QoS 1 permits duplicates. For exactly-once delivery to a subscriber, both the publish and that subscription must use QoS 2.
What is QoS downgrade?
When the QoS a subscriber requested is lower than the QoS the publisher used, the broker delivers to that subscriber at the lower level. The effective end-to-end guarantee is the minimum of the publish hop and the delivery hop.
Which QoS level should I use?
Use QoS 0 for high-frequency, loss-tolerant data on stable links; QoS 1 when you need every message and can tolerate duplicates (the most common choice); and QoS 2 when a duplicate would be harmful and exactly-once is essential, accepting the extra overhead.
Are QoS 1 and 2 messages stored for offline clients?
Yes, but only if the client has a persistent session. Messages sent at QoS 1 or 2 are queued for an offline client and delivered on reconnect, provided the session is persistent. QoS 0 messages and clients with clean sessions do not get this queuing.
