mast — Multi-tenant MQTT broker built on core NATS. One binary, from an edge box to a clustered fleet.

MQTT 3.1.1 & 5.0Shared subscriptionsApache 2.0No JVM

Why it exists

Every open-source MQTT broker that does real multi-tenancy makes you give something up.

BrokerThe catch
EMQXBusiness Source License since 5.9 — clustering more than one node needs a paid licence key.
BifroMQGenuine native multi-tenancy, but it runs on the JVM.
VerneMQApache 2.0 source, but the official packages and images are under a EULA that charges for commercial use.
RabbitMQvhosts, but no QoS 2 and no shared subscriptions.
Mosquitto, NanoMQ, FlashMQ, mochiDo not cluster at all.
mastMQTT 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.

mast

The broker.

charts

Helm charts.

docs

Architecture notes and operational guides.

bench

Load and latency benchmarks.

mochi

The MQTT library the broker embeds, carrying fixes upstream has not merged.