MQTT Fundamentals

MQTT Maximum QoS and Subscription Downgrades

The QoS you ask for is not always the QoS you get. Learn how effective QoS is calculated, how brokers advertise a maximum QoS and grant lower subscription levels, and how to check what your connection really uses.

Publisher at QoS 2 and subscriber at QoS 1 meeting at a broker, showing MQTT maximum QoS downgrade
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

MQTT maximum QoS is the part of quality of service that surprises people most: you can publish at QoS 2 and still receive a message at QoS 0. The level actually used for a delivery depends on the publisher, the subscriber and the broker's own limits. In this article you will learn how effective QoS is calculated, how brokers advertise and enforce a maximum, and how to test exactly what your connection gets.

QoS Is Negotiated Per Hop

MQTT has three QoS levels: 0 (at most once), 1 (at least once) and 2 (exactly once). If those are new to you, start with MQTT QoS levels explained.

The key fact is that QoS is not end-to-end. A message travels over two separate hops:

  1. Publisher to broker, at the QoS the publisher chose for that PUBLISH.
  2. Broker to each subscriber, at a QoS the broker calculates for that subscription.

Each hop has its own handshake. The publisher's PUBACK comes from the broker, not from the subscriber, so a publisher never knows whether any subscriber received the message.

How Effective QoS Is Calculated

For each subscriber, the broker delivers at the lower of two values: the QoS of the incoming PUBLISH and the QoS granted for the matching subscription.

Publish QoS Subscription QoS Delivered QoS
0 2 0
1 0 0
1 2 1
2 1 1
2 2 2

Two consequences matter in practice:

  • Subscribing at QoS 2 does not upgrade anything. If a sensor publishes at QoS 0, every subscriber gets QoS 0 regardless of what it requested.
  • A subscriber can deliberately downgrade. A dashboard that only cares about the latest value can subscribe at QoS 0 to telemetry published at QoS 1, saving acknowledgements on its own link.

If one subscription filter overlaps another (for example plant/# and plant/line1/+), the broker may deliver the message once at the highest matching QoS, or once per subscription in MQTT 5 depending on implementation. Avoid overlapping subscriptions with different QoS when you can.

How Brokers Cap QoS

A broker does not have to support every level for every client. It has two mechanisms to say so.

Granted QoS in SUBACK. When a client subscribes, the broker answers with a SUBACK containing the granted QoS for each filter. It may grant a lower level than requested. In MQTT 3.1.1 the return codes are 0, 1, 2 or 0x80 for failure; in MQTT 5 they are reason codes where 0x00, 0x01 and 0x02 mean granted QoS 0, 1 and 2. A well-written client checks this value instead of assuming its request was honoured.

Maximum QoS in CONNACK (MQTT 5). An MQTT 5 broker that does not support QoS 2, or QoS 1 and 2, includes a Maximum QoS property in CONNACK. If the property is absent, QoS 2 is supported. A client that then publishes above the advertised maximum is violating the protocol, and the broker disconnects it with reason code 0x9B (QoS not supported). In MQTT 3.1.1 there is no such property, so clients find out the hard way.

Brokers cap QoS for good reasons: QoS 2 requires more stored state and four packets per message on each hop, which adds up on shared, multi-tenant infrastructure.

Testing MQTT Maximum QoS on a Public Broker

The public MQTT test broker at broker.connectmq.com supports QoS 0 to 2, so it is a good place to see downgrades caused by the publish/subscribe combination. The -d flag makes the Mosquitto tools print protocol packets, including the granted QoS in SUBACK.

Terminal 1, subscribe requesting QoS 1:

mosquitto_sub -d -h broker.connectmq.com -p 1883 -i mq-sub-u3x7 -q 1 \
  -t 'connectmq/demo/u3x7/#' -F 'qos=%q %t %p'

In the debug output look for a line like Client mq-sub-u3x7 received SUBACK followed by Subscribed (mid: 1): 1. The final number is the granted QoS.

Terminal 2, publish at each level:

for q in 0 1 2; do
  mosquitto_pub -h broker.connectmq.com -p 1883 -i mq-pub-u3x7 -q $q \
    -t connectmq/demo/u3x7/test -m "published at qos $q"
done

Terminal 1 prints qos=0, qos=1 and qos=1. The QoS 2 publish was downgraded to 1 on the way to this subscriber because that is what the subscription asked for. Restart the subscriber with -q 2 and the third line becomes qos=2.

Checking Granted and Maximum QoS in Code

This paho-mqtt 2.x script connects with MQTT 5, prints the broker's advertised Maximum QoS if present, subscribes at QoS 2 and prints what it was granted:

import uuid
import paho.mqtt.client as mqtt

BROKER = "broker.connectmq.com"
TOPIC = "connectmq/demo/u3x7/check"

def on_connect(client, userdata, flags, reason_code, properties):
    max_qos = getattr(properties, "MaximumQoS", None)
    if max_qos is None:
        print("CONNACK: no Maximum QoS property, QoS 2 supported")
    else:
        print(f"CONNACK: broker maximum QoS is {max_qos}")
    client.subscribe(TOPIC, qos=2)

def on_subscribe(client, userdata, mid, reason_code_list, properties):
    rc = reason_code_list[0]
    if rc.is_failure:
        print("subscription refused:", rc)
    else:
        print(f"SUBACK: requested 2, granted {rc.value}")
    client.disconnect()

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id=f"qoscheck-{uuid.uuid4().hex[:8]}",
                     protocol=mqtt.MQTTv5)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.connect(BROKER, 1883, keepalive=30)
client.loop_forever()

Run the same script against your production broker with your real credentials before you rely on a QoS level. It takes seconds and removes guesswork.

QoS for Retained and Will Messages

The same minimum rule applies to messages the broker sends on its own initiative. A retained message is stored with the QoS it was published at, and a new subscriber receives it at the lower of that QoS and its granted subscription QoS. A Last Will is published at the will QoS set in CONNECT, and each subscriber again receives it at the lower of the two. If the broker's maximum QoS is below the will QoS you request, an MQTT 5 broker can refuse the connection with reason code 0x9B, so keep will QoS within the advertised limit.

Practical Checklist

  1. Decide the QoS per topic based on what a lost or duplicated message costs.
  2. Publish at that QoS; subscribers cannot raise it.
  3. Check the granted QoS in SUBACK, and log it when it is lower than requested.
  4. With MQTT 5, read Maximum QoS from CONNACK and clamp your publishes to it.
  5. Design consumers for the QoS you actually get: idempotent for QoS 1 (see MQTT duplicate messages), tolerant of gaps for QoS 0.
  6. Avoid overlapping subscriptions with different QoS on the same client.

Troubleshooting QoS Problems

Client disconnected right after publishing. If the reason code is 0x9B, you published above the broker's maximum. Lower the QoS or move to a plan or broker that supports it. The full list of codes is in MQTT reason codes.

Subscriber receives QoS 0 although it subscribed at QoS 1. The publisher is using QoS 0. Fix it at the source.

Library reports success but messages are lost. Some libraries ignore the granted QoS. Check SUBACK yourself as in the script above.

QoS 2 is slow. Each message needs four packets per hop. For high-rate telemetry QoS 1 with deduplication usually gives better throughput.

The normative rules are in the OASIS MQTT 5.0 specification, sections on SUBACK and CONNACK properties.

QoS Limits on ConnectMQ Plans

ConnectMQ is explicit about QoS per plan. The free Serverless plan supports QoS up to 1, the paid Serverless plan adds QoS 2 along with larger messages and shared subscriptions, and Dedicated, BYOC and Self Managed Enterprise options give you an isolated cluster. The public test broker allows QoS 0 to 2 for experiments. Run the check script with your ConnectMQ credentials to confirm what your connection reports. If you are comparing providers, our overview of a fully managed MQTT service and the MQTT tutorial are good next reads.

Get Started with ConnectMQ

Create a broker for free at app.connectmq.com on the Serverless plan, no credit card required. Start at QoS 0 and 1, then upgrade to the paid plan for QoS 2 when your cloud MQTT broker workload needs exactly-once delivery.

Frequently Asked Questions

What is effective QoS in MQTT?

It is the QoS used to deliver a message to a subscriber: the lower of the publish QoS and the subscription's granted QoS.

Can a subscriber get a higher QoS than the publisher used?

No. A message published at QoS 0 is always delivered at QoS 0, regardless of the subscription.

How do I know the broker's maximum QoS?

In MQTT 5, read the Maximum QoS property in CONNACK; if it is missing, QoS 2 is supported. In MQTT 3.1.1, check the granted QoS in SUBACK.

What happens if I publish above the maximum QoS?

An MQTT 5 broker disconnects the client with reason code 0x9B, QoS not supported.

Related articles

Why Your MQTT Client Keeps Disconnecting (and How to Fix It)

Reconnect loops are one of the most common MQTT problems. Learn the five usual causes, from duplicate client IDs to keep alive timeouts and oversized packets, and follow a diagnosis flow that pinpoints yours.

8 min read

Who Invented MQTT? The History from Oil Pipelines to IoT

MQTT was invented in 1999 by Andy Stanford-Clark of IBM and Arlen Nipper of Arcom to monitor oil pipelines over satellite. Follow its path to open source, OASIS, ISO and today's managed cloud brokers.

7 min read

MQTT Maximum Message Size: Limits and Large Payload Strategies

MQTT can technically carry a 256 MB packet, but brokers, client libraries and devices impose far lower limits. Learn where the real limits are, what happens when you exceed them, and how to send large payloads.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.