IoT Tutorials

MQTT Load Testing Tools: emqtt-bench, MQTTX Bench and Locust Compared

A practical comparison of emqtt-bench, MQTTX Bench and Locust for MQTT load testing, with real commands, a latency and loss probe in Python, the metrics worth collecting and the rules for testing responsibly against a private broker.

Dashboard of mqtt load testing tools showing connection ramp, message throughput and latency curves
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 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.com is 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

  1. Write the goal: for example, "2,000 devices reconnect within 2 minutes; p95 latency stays under an agreed threshold".
  2. Validate with one client: credentials, TLS, topic structure, client ID rules.
  3. Baseline: run the probe on an idle broker.
  4. Ramp in stages: 10, 25, 50, 100 percent of target, holding each stage for several minutes.
  5. Test the nasty cases: everyone reconnecting at once, a subscriber that stops reading, oversized payloads.
  6. Record quota responses separately: 0x97 means you hit a plan limit, which is information, not a crash.
  7. 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.

Related articles

Best MQTT CLI Tools for Testing, Scripting and Debugging

A hands-on comparison of mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, the MQTT.js CLI, curl and mqttui, with the same test run against broker.connectmq.com so you can choose the right tool for scripting and debugging.

8 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.