The maximum MQTT message size in the protocol is 268,435,455 bytes — roughly 256 MB. In practice you will never get that far: brokers, cloud services and client libraries all set lower limits, and some of them drop oversized messages without telling you. This guide explains where each limit lives, how MQTT 5 makes it visible, and how to test the real ceiling of your broker before a firmware image or camera snapshot finds it for you.
Where the 256 MB limit comes from
Every MQTT control packet starts with a fixed header that contains the Remaining Length: the number of bytes that follow. It is encoded as a variable-length integer of at most four bytes, seven bits each, so the largest value is 2^28 − 1 = 268,435,455.
That number covers the whole rest of the packet, not just your data. A PUBLISH also contains:
- the topic name (2-byte length + UTF-8 bytes),
- a 2-byte packet identifier for QoS 1 and 2,
- in MQTT 5, a properties block (content type, user properties, message expiry…).
So the largest possible payload is a little under 256 MB, and it shrinks with long topics and many user properties. The MQTT packet size calculator shows the exact overhead for a given topic, QoS and payload.
The limits you actually hit
| Layer | Where the limit is set | Typical behaviour when exceeded |
|---|---|---|
| Protocol | Remaining Length (4 bytes) | Packet cannot be encoded at all |
| Mosquitto | max_packet_size, older message_size_limit | MQTT 5: disconnect with 0x95; 3.1.1: message dropped or connection closed |
| EMQX | mqtt.max_packet_size (default 1 MB) | Connection closed, error in broker log |
| HiveMQ | max-packet-size in the MQTT config | MQTT 5 clients get 0x95; 3.1.1 clients are disconnected |
| Cloud IoT services | Service quotas | AWS IoT Core caps payloads at 128 KB, Azure IoT Hub device-to-cloud messages at 256 KB — check the current quota pages |
| Client library | Internal buffers, MQTT 5 Maximum Packet Size | Publish error or silent drop on receive |
| Network path | Proxies, load balancers, WebSocket gateways | Timeouts or resets on slow links |
Defaults change between versions, so treat this table as a map of where to look, not as a list of guaranteed numbers. Read the configuration reference of the exact version you run.
Mosquitto configuration
Mosquitto 2.x has two related options:
# Maximum size of any MQTT packet the broker accepts, in bytes.
# MQTT 5 clients are told this value in CONNACK.
max_packet_size 1048576
# Older option: maximum PUBLISH payload size. Deprecated in favour of
# max_packet_size, but still found in many existing configs.
# message_size_limit 1048576If neither is set, Mosquitto does not impose a lower limit than the protocol, which means one client can make the broker allocate hundreds of megabytes. Setting an explicit limit is part of a sane production config — the mosquitto.conf generator includes it.
MQTT 5 Maximum Packet Size
MQTT 3.1.1 has no way to negotiate size. MQTT 5 adds a Maximum Packet Size property in both directions:
- CONNECT → broker: the client announces the largest packet it is willing to receive. The broker must not send anything bigger; if a matching message is too large for this subscriber, the broker discards it for that client only.
- CONNACK → client: the broker announces the largest packet it accepts. A well-behaved client library refuses to send bigger packets. If one is sent anyway, the broker disconnects with reason code 0x95 Packet too large.
That second case is the error people search for. Its meaning and the related codes are listed in the MQTT reason codes reference and explained in detail in MQTT packet too large.
Quick test with mosquitto_pub
Generate files of known sizes and push them through the broker. -f sends a file as the payload; -N stops mosquitto_sub from appending a newline, so the byte count stays exact.
HOST=broker.connectmq.com
TOPIC="testmqtt/payload-$RANDOM/blob"
for size in 64K 512K 1M 4M; do
head -c "$size" /dev/urandom > "/tmp/blob-$size"
done
# Terminal 1: receive one message and count its bytes
mosquitto_sub -h "$HOST" -t "$TOPIC" -V mqttv5 -C 1 -W 30 -N | wc -c
# Terminal 2: send one size at a time
mosquitto_pub -h "$HOST" -t "$TOPIC" -V mqttv5 -q 1 -f /tmp/blob-1M -d-d prints every packet sent and received, so you can see whether the PUBLISH left the client and whether the connection was closed right after it. If wc -c prints the file size, the message made it; if mosquitto_sub times out after 30 seconds, it did not. To simulate a constrained subscriber, add -D connect maximum-packet-size 65536 to mosquitto_sub and watch larger messages vanish for that client only.
An automated size sweep in Python
For a repeatable answer, this paho-mqtt 2.x script publishes payloads of increasing size at QoS 1, verifies each one with a SHA-256 hash and stops at the first failure. It also prints the broker's advertised limit.
import hashlib, os, threading, time, uuid
import paho.mqtt.client as mqtt
HOST, PORT = "broker.connectmq.com", 1883
TOPIC = f"testmqtt/payload-{uuid.uuid4().hex[:8]}/blob"
SIZES = [1_024, 65_536, 262_144, 1_048_576, 4_194_304, 16_777_216]
TIMEOUT = 30
result, got = {}, threading.Event()
def on_connect(client, userdata, flags, reason_code, properties):
limit = getattr(properties, "MaximumPacketSize", None)
print(f"connected: {reason_code}, broker max packet = {limit or 'not advertised'}")
def on_disconnect(client, userdata, flags, reason_code, properties):
print(f"disconnected: {reason_code}")
def on_message(client, userdata, msg):
result["digest"] = hashlib.sha256(msg.payload).hexdigest()
got.set()
def make_client(name):
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"{name}-{uuid.uuid4().hex[:6]}", protocol=mqtt.MQTTv5)
# c.username_pw_set(os.environ["MQTT_USER"], os.environ["MQTT_PASS"])
c.on_connect, c.on_disconnect, c.on_message = on_connect, on_disconnect, on_message
c.connect(HOST, PORT)
c.loop_start()
return c
sub = make_client("sweep-sub")
sub.subscribe(TOPIC, qos=1)
pub = make_client("sweep-pub")
time.sleep(2)
for size in SIZES:
payload = os.urandom(size)
got.clear()
start = time.perf_counter()
info = pub.publish(TOPIC, payload, qos=1)
if info.rc != mqtt.MQTT_ERR_SUCCESS:
print(f"{size:>10} B FAIL publish rc={info.rc}")
break
if not got.wait(TIMEOUT):
print(f"{size:>10} B FAIL not delivered within {TIMEOUT}s")
break
ok = result["digest"] == hashlib.sha256(payload).hexdigest()
print(f"{size:>10} B {'OK ' if ok else 'BAD '} {(time.perf_counter() - start) * 1000:.0f} ms")
pub.loop_stop(); sub.loop_stop()Typical outputs and what they mean:
- All sizes OK — the broker accepts at least 16 MB. Decide whether that is actually what you want.
- Delivered up to N, then "not delivered", no disconnect — the broker drops oversized messages silently, or the subscriber's own limit kicked in.
- "disconnected: Packet too large" — an MQTT 5 broker enforcing
max_packet_size. The previous size that passed is your practical ceiling. - Latency grows sharply with size — you are measuring bandwidth, not the broker. Check the numbers against your uplink speed.
Run the sweep once over TCP and once over WebSockets (port 8083 or 8084 with path /mqtt): proxies in front of WebSocket listeners sometimes have their own frame or buffer limits. You can also try individual payloads interactively in the online MQTT client.
Big payloads have side effects
Even when the broker accepts a 10 MB message, it is not free:
- Head-of-line blocking. MQTT runs over one TCP connection. While a large PUBLISH is being transmitted, nothing else on that connection moves — including small, urgent commands and PINGREQ packets. On a slow cellular link this can trigger keep-alive timeouts.
- Memory. The broker usually holds the full message in memory, once per queued subscriber for offline persistent sessions. Retained large messages stay there indefinitely.
- Retries. At QoS 1 or 2, a dropped connection means the whole packet is sent again — there is no resume.
- Fan-out. One 5 MB message to 200 subscribers is 1 GB of egress traffic. See MQTT bandwidth overhead for the arithmetic.
Design patterns for large data
- Keep messages small by default. Telemetry, events and commands should be bytes or kilobytes. Binary encodings such as CBOR or Protobuf shrink JSON payloads considerably.
- Chunk large files. Split into numbered parts (for example 64 KB each) on
…/file/<id>/<n>, send a manifest with the total size and hash, and reassemble on the receiver. File transfer over MQTT walks through a full implementation. - Send a pointer, not the file. Upload to object storage over HTTP and publish the URL plus metadata over MQTT. This is the usual choice for camera images and firmware.
- Set an explicit limit on the broker a bit above your largest legitimate message, so a misbehaving client cannot exhaust memory.
Publish a test payload in your browser
The protocol ceiling of 256 MB is a theoretical number; the limit that matters is the smallest one on your path, from client buffer to broker config to cloud quota. Measure it with a size sweep, make it explicit in your broker configuration, and design anything bigger as chunks or pointers instead of a single message.
Frequently asked questions
What is the maximum MQTT message size?
The protocol allows a control packet of up to 268,435,455 bytes (about 256 MB), because the Remaining Length field is at most four bytes. The usable payload is slightly smaller, and almost every broker or cloud service enforces a much lower limit.
Why does my large MQTT message disappear without an error?
With MQTT 3.1.1 there is no way for a broker to reject a single PUBLISH with a reason, so many brokers silently drop oversized messages or close the connection. Use MQTT 5, read the Maximum Packet Size from CONNACK and watch for reason code 0x95 Packet too large.
Should I send files or images over MQTT?
Small files of a few hundred kilobytes are fine on most brokers. For larger files, split them into numbered chunks or publish a URL to object storage instead, so one big message does not block the connection or exhaust broker memory.

