Why it exists
Every open-source MQTT broker that does real multi-tenancy makes you give something up.
| Broker | The catch |
|---|---|
| EMQX | Business Source License since 5.9 — clustering more than one node needs a paid licence key. |
| BifroMQ | Genuine native multi-tenancy, but it runs on the JVM. |
| VerneMQ | Apache 2.0 source, but the official packages and images are under a EULA that charges for commercial use. |
| RabbitMQ | vhosts, but no QoS 2 and no shared subscriptions. |
| Mosquitto, NanoMQ, FlashMQ, mochi | Do not cluster at all. |
| mast | MQTT 5 with shared subscriptions, tenant isolation with per-tenant limits, free clustering, no JVM, and a single binary you can run standalone at a customer site. |
Run it
No flags needed for the common case. The roles exist for when you outgrow it.
# all-in-one: MQTT and storage in one process, no cluster
mast
# or split the tiers once the MQTT side needs to scale
# independently of Raft
mast --role=core # Raft and the KV buckets, stable peers, a volume
mast --role=edge # MQTT listeners, stateless, joins core as a leaf node
You do not need the split until you are big. Up to roughly ten nodes, run every process identical and let them mesh.
How it is put together
Core NATS moves messages
Live fan-out rides core NATS, which handles tens of millions of subjects at roughly a gigabyte of memory per million subscriptions.
A key-value store holds keys
Sessions, retained messages and offline queues live in a handful of JetStream KV buckets. Never a consumer per subscription: consumers are Raft state machines, and a real fleet would want hundreds of thousands.
Isolation is structural
Every topic is mounted under its tenant before validation, authorization or subscription sees it, and unmounted on the way out. A client never learns the prefix exists.
Authentication, two ways
Ask an HTTP service, or verify a signed token locally. They compose: authenticate from a token while topic decisions still go to a policy server.
Replacing EMQX
mast speaks EMQX v5's HTTP auth wire, so an auth service written for EMQX works unchanged.
[auth]
mode = "http"
[auth.http]
wire = "emqx" # EMQX's request and response shape
authn_url = "http://soteria:9999/v2/auth"
authz_url = "http://soteria:9999/v2/acl"
on_error = "deny" # EMQX_AUTHORIZATION__NO_MATCH
Topics reach the authorization endpoint unmounted, so existing ACL rules keep working. The migration guide has the full mapping and the four traps that actually fired doing it — is_superuser, multipath TCP on OpenShift, Helm's map merging, and SCC uid ranges.
Where the project is
Early, and honest about it. The broker runs, moves messages across a cluster with tenant isolation, honours QoS 0 through 2, replays retained messages, delivers wills, and carries a persistent session from one node to another.
Gaps are tracked as issues rather than described as a roadmap. The parity label is what an MQTT client is entitled to assume and does not yet get. There is no dashboard and no management API, and no plan for a rule engine or data bridges.
One limit is worth knowing before you build on it. QoS 1 and 2 hold across a cluster: a publish is stored on a replicated stream before it is acknowledged, and a node cut off from the core catches up when it returns. Shared subscriptions whose members span nodes are still best-effort. Delivery guarantees sets out what each QoS promises hop by hop, what it costs, and where it still falls short.
