A broker that works with one test client can still fall over with a thousand devices. Load testing an MQTT broker answers three questions before production does: how many concurrent connections it accepts, how many messages per second it can route, and what latency your subscribers see while it is busy.
This guide shows how to plan a benchmark, run it with two open-source tools and a Python script, and read the numbers without fooling yourself.
What to measure
| Metric | What it tells you | Typical target |
|---|---|---|
| Connection rate | How fast the broker accepts new CONNECTs (reconnect storms!) | Hundreds per second |
| Concurrent connections | Memory and file-descriptor limits | Your fleet size × 1.5 |
| Publish throughput | Messages per second accepted from publishers | Peak traffic × 2 |
| Fan-out throughput | Messages per second delivered to subscribers | Depends on topic design |
| End-to-end latency (p50 / p99) | Time from publish to delivery | p99 under 50 ms in-region |
| Error rate | Disconnects, dropped QoS 0 messages, timeouts | Close to zero |
Always benchmark the same QoS, payload size and topic structure that your devices use. A test that publishes 10-byte QoS 0 messages to one topic says nothing about 2 KB QoS 1 telemetry spread over 50,000 topics. See MQTT QoS levels for how each level changes the packet count.
Pick the right target
Never run a heavy benchmark against a shared public sandbox — the numbers are meaningless (you are competing with every other user) and the operators will throttle or block you. Smoke-test there, then point the load at an instance you control.
For the examples below we use broker.connectmq.com; replace the host, port and credentials with your own instance.
1. emqtt-bench
emqtt-bench is an Erlang tool that can open tens of thousands of connections from one machine. The easiest way to run it is Docker:
docker run --rm -it emqx/emqtt-bench conn \
-h broker.connectmq.com -p 1883 \
-c 2000 -R 200 \
-u "$MQTT_USER" -P "$MQTT_PASS"Then measure throughput with a subscriber group and a publisher group running at the same time:
docker run --rm -it emqx/emqtt-bench sub \
-h broker.connectmq.com -p 1883 \
-c 100 -t 'bench/%i/#' -q 1docker run --rm -it emqx/emqtt-bench pub \
-h broker.connectmq.com -p 1883 \
-c 100 -I 10 -t 'bench/%i/data' -s 256 -q 1%i expands to the client index, so every publisher has its own topic and its own subscriber — a realistic one-to-one telemetry pattern. 100 publishers × 100 msg/s gives a target of 10,000 messages per second. emqtt-bench prints sent and received rates every second; if the received rate falls behind the sent rate, the broker (or your network) is the bottleneck.
2. mqtt-benchmark (Go)
If you prefer a single binary, mqtt-benchmark publishes a fixed number of messages and reports throughput and latency statistics:
go install github.com/krylovsk/mqtt-benchmark@latest
mqtt-benchmark \
--broker tcp://broker.connectmq.com:1883 \
--clients 50 --count 1000 --size 512 --qos 1 \
--topic bench/go --format textThe summary at the end includes messages per second per client, the aggregate rate and mean / standard deviation of publish times — a quick way to compare two brokers or two configurations.
3. A latency script you can trust
Throughput tools measure how fast messages go in. Users feel how fast they come out. This Python script (paho-mqtt 2.x) embeds the send time in every payload and computes end-to-end percentiles on the subscriber side:
import json, statistics, time, uuid
import paho.mqtt.client as mqtt
HOST, PORT = "broker.connectmq.com", 1883
TOPIC = f"bench/{uuid.uuid4().hex[:8]}/latency"
N, RATE = 2000, 200 # messages, messages per second
samples = []
def on_message(client, userdata, msg):
sent = json.loads(msg.payload)["t"]
samples.append((time.perf_counter() - sent) * 1000)
sub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
sub.on_message = on_message
sub.connect(HOST, PORT)
sub.subscribe(TOPIC, qos=1)
sub.loop_start()
pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(1) # let the subscription settle
for i in range(N):
pub.publish(TOPIC, json.dumps({"i": i, "t": time.perf_counter()}), qos=1)
time.sleep(1 / RATE)
time.sleep(3)
samples.sort()
p = lambda q: samples[int(len(samples) * q) - 1]
print(f"received {len(samples)}/{N}")
print(f"p50 {p(0.50):.1f} ms p95 {p(0.95):.1f} ms p99 {p(0.99):.1f} ms max {samples[-1]:.1f} ms")
print(f"mean {statistics.mean(samples):.1f} ms")Because publisher and subscriber run in the same process, they share one clock — no NTP drift in the result. Run it once idle and once while emqtt-bench is hammering the broker: the difference is the latency your devices will see at peak.
Reading the results
- Received < sent at QoS 0 means the broker or the client dropped messages under pressure. Retry at QoS 1 to see whether it is a delivery or a capacity problem.
- p99 far above p50 points to garbage-collection pauses, disk-backed persistence or a saturated network link. Check broker CPU and the load generator's CPU — a maxed-out benchmark machine is the most common false result.
- Connection test stalls at a round number (1,024, 4,096, 65,535) — you hit a file-descriptor or ephemeral-port limit on the load generator, not the broker. Raise
ulimit -nor spread the load across several machines or source IPs. - Disconnects during the run — look for keep-alive timeouts or the broker's per-client inflight / queue limits. See MQTT keep alive and connection errors.
A repeatable load-test checklist
- Smoke-test connectivity first with the online MQTT client or mosquitto_pub / mosquitto_sub.
- Fix the scenario: number of clients, QoS, payload size, topic layout, retained or not.
- Ramp up gradually (10 %, 50 %, 100 %, 150 % of expected peak) and hold each step for a few minutes.
- Record broker CPU, memory and connection count next to the client-side numbers.
- Repeat the run twice; only trust results that reproduce.
- Re-run after every broker upgrade or configuration change.
Smoke-test the broker in your browser first
Load testing turns "it works on my laptop" into a number you can plan capacity around. Start small, measure latency where your users are, and keep the scripts in your repository so the next benchmark takes minutes instead of days.
Frequently asked questions
Can I load test a public MQTT broker?
Only lightly. Public sandboxes are shared and rate-limited, and heavy traffic degrades the service for everyone else. Run real load tests against a broker you own or a private instance provided for testing.
What is a good MQTT latency?
On the same region, a healthy broker delivers QoS 0 messages end-to-end in a few milliseconds and QoS 1 in under 20 ms at moderate load. Track p99, not the average — spikes are what break devices.
Which tool is best for MQTT benchmarks?
emqtt-bench is the most complete open-source option for connections, publish and subscribe load. mqtt-benchmark (Go) is simpler to install. A small custom script is best when you need your exact payloads and topic layout.



