MQTT load testing tools tell you whether your system holds up when thousands of devices connect at once, a firmware bug floods a topic or a backend subscriber falls behind. This guide compares emqtt-bench, MQTTX Bench and Locust, shows the commands for each, and adds a small Python probe that measures end-to-end latency and message loss. You will also learn which metrics matter and how to test responsibly, always against your own private broker, never a shared public one.
First Rule: Test Your Own Broker
Picking from the available MQTT load testing tools is the easy part; aiming them at the right target is what matters. Load tests generate traffic that looks like an attack. Run them only against infrastructure you own or are explicitly allowed to test.
- Do not load test the public broker.
broker.connectmq.comis a shared test service with strict per-client limits (10 messages per second per client, 10 subscriptions per client, 64 KB messages). A load test there measures those limits, not your system, and degrades it for everyone else. - Use a private broker sized for the test. On ConnectMQ, create a dedicated broker for load testing so production devices are not affected.
- Know your plan's quotas before you start. The free Serverless plan allows up to 25 connections and QoS up to 1; paid Serverless allows up to 1,000 connections, QoS 2 and 1 MB messages; Dedicated runs on an isolated cluster. Exceeding a quota is not a broker failure: MQTT 5 clients receive reason code 0x97 (quota exceeded), which your test should record separately from real errors.
- Check client ID rules. ConnectMQ binds client IDs to the credentials it issues, while load tools generate IDs from a prefix and a counter. Confirm in the portal which client IDs your test credentials allow and configure the tool's client ID option to match, or every connection will be rejected as not authorized.
- Mind the protection layers. Managed brokers apply flood protection and block IPs after repeated failed logins. A misconfigured test with wrong credentials can get your test machine's IP blocked, so validate credentials with a single client first.
What to Measure
Throughput alone is a misleading number. Collect these, and decide the pass criteria before you run anything:
| Metric | Why it matters | How to get it |
|---|---|---|
| Connection rate and success | Fleet reconnect storms after an outage are the hardest moment for a broker | Tool reports connects per second, failures and reason codes |
| Publish throughput | Whether producers can push the expected rate | Messages per second sent and acknowledged (QoS 1/2) |
| Delivered throughput | Whether consumers keep up | Messages per second received by subscribers |
| End-to-end latency | User-visible delay, percentiles not averages | Timestamp in payload, compute at subscriber (p50, p95, p99) |
| Message loss and duplicates | QoS behavior under stress | Sequence numbers per publisher |
| Load-generator health | A saturated test machine produces fake results | CPU, memory, open sockets, network on the generator |
Always watch the load generator. If its CPU is at 100 percent, latency numbers reflect your laptop, not the broker. Split large tests across several machines.
emqtt-bench
emqtt-bench is a lightweight Erlang tool from the EMQX project, designed for opening many connections with very little memory per client. It has three modes: conn, pub and sub.
# Ramp up 500 connections, one every 10 ms
emqtt_bench conn -h YOUR_BROKER_HOST -p 1883 -c 500 -i 10 \
-u YOUR_USERNAME -P YOUR_PASSWORD
# 50 subscribers on a wildcard, QoS 1
emqtt_bench sub -h YOUR_BROKER_HOST -p 1883 -c 50 -i 10 \
-t 'loadtest/#' -q 1 -u YOUR_USERNAME -P YOUR_PASSWORD
# 200 publishers, each sending a 256-byte message every 1000 ms to its own topic
emqtt_bench pub -h YOUR_BROKER_HOST -p 1883 -c 200 -i 10 -I 1000 \
-t 'loadtest/device/%i' -s 256 -q 1 -u YOUR_USERNAME -P YOUR_PASSWORD
Key flags: -c client count, -i interval between new connections in milliseconds, -I interval between publishes per client, -t topic with %i (client sequence number) and %c (client ID) placeholders, -s payload size, -q QoS. Add --ssl and port 8883 to test TLS, which you should, because TLS handshakes are often the real cost during reconnect storms. The tool prints rates every few seconds.
Strengths: very efficient per connection, ideal for connection-count and reconnect-storm tests. Weaknesses: payloads are synthetic, and latency measurement is limited, so pair it with a probe (below).
MQTTX Bench
MQTTX CLI includes a bench command with the same three modes and friendlier output. If your team already uses MQTTX, it is the easiest start.
mqttx bench conn -c 200 -i 10 -h YOUR_BROKER_HOST -p 8883 -l mqtts \
-u YOUR_USERNAME -P YOUR_PASSWORD
mqttx bench sub -c 20 -t 'loadtest/#' -q 1 -h YOUR_BROKER_HOST -p 8883 -l mqtts \
-u YOUR_USERNAME -P YOUR_PASSWORD
mqttx bench pub -c 100 -i 10 -im 1000 -t 'loadtest/device/%i' -q 1 \
-m '{"temp": 21.5}' -h YOUR_BROKER_HOST -p 8883 -l mqtts \
-u YOUR_USERNAME -P YOUR_PASSWORD
Here -i is the connection interval and -im the per-client message interval. Because it runs on Node.js, each client costs more memory than emqtt-bench, so very large connection counts need more generator machines. Check mqttx bench pub --help in your version, since options evolve.
Locust
Locust is a Python load testing framework. It has no native MQTT support, but you can write a User class around paho-mqtt and report results to Locust's web UI. That makes it the most flexible option: realistic payloads, per-device behavior, ramp shapes and assertions are all just Python.
# locustfile.py (pip install locust paho-mqtt)
import json
import os
import random
import time
import uuid
import paho.mqtt.client as mqtt
from locust import User, between, events, task
HOST = os.environ.get("MQTT_HOST", "YOUR_BROKER_HOST")
USER = os.environ.get("MQTT_USER", "YOUR_USERNAME")
PASS = os.environ.get("MQTT_PASS", "YOUR_PASSWORD")
class MqttDevice(User):
wait_time = between(1, 2)
def on_start(self):
self.device_id = uuid.uuid4().hex[:8]
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"lt-{self.device_id}",
protocol=mqtt.MQTTv5)
self.client.username_pw_set(USER, PASS)
self.client.tls_set()
start = time.perf_counter()
try:
self.client.connect(HOST, 8883, keepalive=30)
self.client.loop_start()
events.request.fire(request_type="MQTT", name="connect",
response_time=(time.perf_counter() - start) * 1000,
response_length=0, exception=None, context={})
except Exception as exc:
events.request.fire(request_type="MQTT", name="connect",
response_time=(time.perf_counter() - start) * 1000,
response_length=0, exception=exc, context={})
raise
@task
def publish_telemetry(self):
payload = json.dumps({"id": self.device_id,
"temp": round(random.uniform(18, 26), 2),
"ts": time.time()})
start = time.perf_counter()
info = self.client.publish(f"loadtest/device/{self.device_id}", payload, qos=1)
exc = None
try:
info.wait_for_publish(timeout=5) # waits for PUBACK at QoS 1
if not info.is_published():
exc = TimeoutError("no PUBACK within 5 s")
except Exception as e:
exc = e
events.request.fire(request_type="MQTT", name="publish qos1",
response_time=(time.perf_counter() - start) * 1000,
response_length=len(payload), exception=exc, context={})
def on_stop(self):
self.client.loop_stop()
self.client.disconnect()
Run it with locust -f locustfile.py --headless -u 100 -r 5 --run-time 10m, where -u is the number of simulated devices and -r the spawn rate per second. Each device runs its own paho network thread, so Locust is heavier per connection than emqtt-bench; use it for behavior-rich tests with hundreds of devices rather than raw connection counts. The publish timing here measures the PUBACK round trip, which is a good proxy for broker responsiveness.
A Latency and Loss Probe in Python
None of the tools above give you trustworthy end-to-end latency and loss by default. Run this probe alongside any load test: it publishes sequenced, timestamped messages and measures what arrives.
# probe.py (pip install paho-mqtt)
import json, os, statistics, time, uuid
import paho.mqtt.client as mqtt
HOST, PORT = os.environ.get("MQTT_HOST", "YOUR_BROKER_HOST"), 8883
USER, PASS = os.environ.get("MQTT_USER", "YOUR_USERNAME"), os.environ.get("MQTT_PASS", "YOUR_PASSWORD")
TOPIC = f"loadtest/probe/{uuid.uuid4().hex[:6]}"
COUNT, INTERVAL = 600, 0.5
latencies, seen = [], set()
def on_message(client, userdata, msg):
data = json.loads(msg.payload)
latencies.append((time.time() - data["ts"]) * 1000)
seen.add(data["seq"])
def make(name):
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"probe-{name}-{uuid.uuid4().hex[:6]}")
c.username_pw_set(USER, PASS)
c.tls_set()
c.connect(HOST, PORT, keepalive=30)
c.loop_start()
return c
sub = make("sub")
sub.on_message = on_message
sub.subscribe(TOPIC, qos=1)
time.sleep(1)
pub = make("pub")
for seq in range(COUNT):
pub.publish(TOPIC, json.dumps({"seq": seq, "ts": time.time()}), qos=1)
time.sleep(INTERVAL)
time.sleep(5)
q = statistics.quantiles(latencies, n=100)
print(f"received {len(seen)}/{COUNT}, lost {COUNT - len(seen)}")
print(f"latency ms p50={q[49]:.1f} p95={q[94]:.1f} p99={q[98]:.1f} max={max(latencies):.1f}")
Publisher and subscriber run on the same machine, so clock skew does not distort latency. Run the probe once on an idle broker for a baseline, then during each load stage; the difference is what the load costs. If your credentials allow only specific client IDs, replace the generated IDs with your issued ones.
Comparison Summary
| emqtt-bench | MQTTX Bench | Locust + paho | |
|---|---|---|---|
| Best for | Connection counts, reconnect storms | Quick tests with readable output | Realistic device behavior, custom logic |
| Resource cost per client | Very low | Moderate | Higher |
| Payload realism | Synthetic, fixed size | Fixed or templated | Anything you code |
| Latency and loss | Limited | Limited | Via custom code and probe |
| Reporting | Console rates | Console rates | Web UI, CSV export |
| Learning curve | Low | Low | Moderate (Python) |
A Responsible Test Plan
- Write the goal: for example, "2,000 devices reconnect within 2 minutes; p95 latency stays under an agreed threshold".
- Validate with one client: credentials, TLS, topic structure, client ID rules.
- Baseline: run the probe on an idle broker.
- Ramp in stages: 10, 25, 50, 100 percent of target, holding each stage for several minutes.
- Test the nasty cases: everyone reconnecting at once, a subscriber that stops reading, oversized payloads.
- Record quota responses separately: 0x97 means you hit a plan limit, which is information, not a crash.
- Clean up: delete retained test messages and disconnect all clients.
If the results show you need more connections or isolation than your current plan offers, that is the signal to move up a tier, for example from Serverless to Dedicated. The managed MQTT broker overview and the guide to choosing a cloud MQTT broker explain the trade-offs. For functional testing before load testing, see testing MQTT clients, and for generating realistic traffic at low volume use an MQTT device simulator. Command-line basics are covered in MQTT CLI tools, and the MQTT client libraries guide helps if you want to build a custom load generator in another language.
Get Started with ConnectMQ
Create a dedicated test broker at app.connectmq.com on the free Serverless plan, no credit card required, and validate your test scripts at small scale. When you are ready for larger runs, upgrade the same broker setup and keep the portal's dashboard open to watch connections while the test runs.
Frequently Asked Questions
Can I load test the public MQTT broker?
No. Public brokers are shared and rate-limited per client, so results are meaningless and other users are affected. Use your own private broker.
Which MQTT load testing tool is best?
emqtt-bench for large connection counts, MQTTX Bench for quick readable tests, and Locust when you need realistic device behavior. Many teams use emqtt-bench for scale plus a small probe for latency.
Why do my load test clients get rejected with quota exceeded?
You reached a limit of your plan, such as the maximum number of connections. MQTT 5 clients receive reason code 0x97; reduce the load or move to a plan with higher limits.
Should load tests use TLS?
Yes, if production uses TLS. Handshakes add significant CPU work on both sides during reconnect storms, and testing without TLS hides that cost.



