11 KiB
wolfMQTT Broker
wolfMQTT includes a lightweight MQTT broker suitable for embedded and resource-constrained environments. It serves both MQTT v3.1.1 and v5.0 clients, with optional TLS via wolfSSL, optional WebSocket transport, and optional encrypted persistence. The broker uses non-blocking sockets driven by a single select() loop, so it runs without threads.
Features
- QoS 0, QoS 1, and QoS 2 publish/subscribe (full QoS 2 flow with PUBREC/PUBREL/PUBCOMP)
- Retained messages
- Last Will and Testament (LWT), including v5 Will Delay Interval
- Wildcard subscriptions (
+and#) - Username/password authentication
- MQTT v5 ordering and Receive Maximum (per-subscriber inflight shaping)
- TLS support (requires wolfSSL with
--enable-tls) - WebSocket / secure WebSocket transport (requires libwebsockets; see the WebSocket section of the main README.md)
- Clean session handling with subscription persistence
- Keep-alive monitoring with automatic client disconnect
- Unique client ID enforcement (existing session takeover)
- Optional on-disk persistence of sessions, subscriptions, retained messages, and offline queues, with optional AES-GCM encryption-at-rest
- Static memory mode (
WOLFMQTT_STATIC_MEMORY) for zero-malloc operation
Quick start
With autotools:
./configure --enable-broker
make
./src/mqtt_broker -p 1883
With CMake:
cmake .. -DWOLFMQTT_BROKER=yes
cmake --build .
For TLS:
./configure --enable-broker --enable-tls
make
./src/mqtt_broker -p 8883 -t -A ca-cert.pem -K server-key.pem -c server-cert.pem
Run ./src/mqtt_broker -h to see the options compiled into your build.
Command-line options
usage: mqtt_broker [-p port] [-v level] [-u user] [-P pass]
[-t] [-s port] [-V ver] [-c cert] [-K key] [-A ca]
[-w port] [-D dir] [-E source]
| Option | Available when | Description |
|---|---|---|
-p <port> |
always | Plain (non-TLS) port (default: 1883) |
-v <level> |
always | Log level: 1=error, 2=info (default), 3=debug |
-u <user> |
auth build | Username for authentication |
-P <pass> |
auth build | Password for authentication |
-t |
TLS build | Enable the TLS listener |
-s <port> |
TLS build | TLS port (default: 8883) |
-V <ver> |
TLS build | TLS version: 12=TLS 1.2, 13=TLS 1.3 (default: auto) |
-c <file> |
TLS build | Server certificate file (PEM) |
-K <file> |
TLS build | Server private key file (PEM) |
-A <file> |
TLS build | CA certificate for mutual TLS (PEM) |
-w <port> |
WebSocket build | WebSocket listen port (enables WebSocket) |
-D <dir> |
persist build | Persistent storage directory (enables persistence; default /var/lib/wolfmqtt on Linux or /private/var/lib/wolfmqtt on macOS) |
-E <source> |
encrypt + dev-key build | Encryption key source. Only dev is recognized, selecting the development hard-coded key. NOT FOR PRODUCTION. |
Build options
All broker features are enabled by default and can be disabled at build time to reduce code and memory footprint on constrained platforms.
| Feature | Autotools | CMake | Define |
|---|---|---|---|
| Broker support | --enable-broker |
-DWOLFMQTT_BROKER=yes |
WOLFMQTT_BROKER |
| Retained messages | --disable-broker-retained |
-DWOLFMQTT_BROKER_RETAINED=no |
WOLFMQTT_BROKER_NO_RETAINED |
| Last Will and Testament | --disable-broker-will |
-DWOLFMQTT_BROKER_WILL=no |
WOLFMQTT_BROKER_NO_WILL |
| Wildcard subscriptions | --disable-broker-wildcards |
-DWOLFMQTT_BROKER_WILDCARDS=no |
WOLFMQTT_BROKER_NO_WILDCARDS |
| Authentication | --disable-broker-auth |
-DWOLFMQTT_BROKER_AUTH=no |
WOLFMQTT_BROKER_NO_AUTH |
| Logging | --disable-broker-log |
-DWOLFMQTT_BROKER_LOG=no |
WOLFMQTT_BROKER_NO_LOG |
| Plain-text listener | --disable-broker-insecure |
-DWOLFMQTT_BROKER_INSECURE=no |
WOLFMQTT_BROKER_NO_INSECURE |
The maximum QoS the broker negotiates is capped by --enable-max-qos=<0,1,2> (default 2). Setting it to 1 or 0 compiles out the QoS 2 state machine and shrinks the broker.
Static memory tuning
When built with WOLFMQTT_STATIC_MEMORY, the broker uses fixed-size arrays instead of dynamic allocation. The limits below can be overridden via CFLAGS at build time.
| Macro | Default | Description |
|---|---|---|
BROKER_MAX_CLIENTS |
8 | Maximum concurrent client connections |
BROKER_MAX_SUBS |
32 | Maximum total subscriptions across all clients |
BROKER_MAX_RETAINED |
16 | Maximum retained messages |
BROKER_MAX_CLIENT_ID_LEN |
64 | Maximum client ID length |
BROKER_MAX_USERNAME_LEN |
64 | Maximum username length |
BROKER_MAX_PASSWORD_LEN |
64 | Maximum password length |
BROKER_MAX_FILTER_LEN |
128 | Maximum subscription filter length |
BROKER_MAX_TOPIC_LEN |
128 | Maximum topic name length |
BROKER_MAX_PAYLOAD_LEN |
4096 | Maximum retained message payload |
BROKER_MAX_WILL_PAYLOAD_LEN |
256 | Maximum LWT payload |
BROKER_MAX_PENDING_WILLS |
4 | Maximum queued pending wills |
BROKER_MAX_INBOUND_QOS2 |
16 | Concurrent inbound QoS 2 packet IDs per client |
BROKER_MAX_STATIC_ORPHAN_SESSIONS |
BROKER_MAX_CLIENTS |
Persistent sessions with a bounded offline queue |
BROKER_MAX_STATIC_OFFLINE_MSGS_PER_SUB |
8 | Queued QoS 1/2 messages per static persistent session |
BROKER_MAX_STATIC_OFFLINE_DATA_LEN |
256 | Maximum property and payload bytes per queued message |
BROKER_RX_BUF_SZ |
4096 | Per-client receive buffer size |
BROKER_TX_BUF_SZ |
4096 | Per-client transmit buffer size |
BROKER_TIMEOUT_MS |
1000 | select() timeout |
BROKER_LISTEN_BACKLOG |
128 | Listen queue depth |
Retaining a message is best-effort. A RETAIN=1 PUBLISH whose retained copy
cannot be stored (the retained table is full at BROKER_MAX_RETAINED, the
payload exceeds BROKER_MAX_PAYLOAD_LEN, or an allocation fails) is still
delivered to every current subscriber and acknowledged with success at any QoS
or protocol level. Only the retained copy is skipped, so a later subscriber will
not receive that message until the topic is published again with room to store
it. The skipped retained copy is logged broker-side. This matches how servers
such as Mosquitto treat a full retained store: live delivery is never sacrificed
to retention. Raise BROKER_MAX_RETAINED (or BROKER_MAX_PAYLOAD_LEN) if
retained-topic capacity matters for your deployment.
The static offline queue is broker-owned fixed storage. Its dominant RAM cost
is approximately sessions * messages * (topic length + data length) bytes,
plus queue metadata. A new persistent CONNECT is refused when all session slots
are occupied; existing persistent sessions are never evicted to admit it. An
MQTT v3.1.1 CleanSession=0 Session reserves its slot until that Client Identifier
reconnects with CleanSession=1. With the default limits, the static Session
queues add approximately 27 KB to MqttBroker. If a matching QoS 1/2
publication cannot fit in a Session queue, the broker preserves the existing
backlog and drops that Session's new delivery without disconnecting it. A
connected client whose Session queue is already full is disconnected so it can
resume the preserved backlog later. Capacity for one Session does not change
the publisher's ACK. Increase BROKER_MAX_STATIC_OFFLINE_DATA_LEN when durable
Sessions must retain messages whose property and payload bytes exceed 256.
The per-subscriber inflight window is bounded by BROKER_MAX_INFLIGHT_PER_SUB
and, for MQTT v5, the client's Receive Maximum. Define
BROKER_MAX_INFLIGHT_PER_SUB=1 to force strict serial delivery.
Persistence
Build with --enable-broker-persist to persist sessions, subscriptions,
retained messages, and dynamic-memory offline queues across restarts. Static
Session queues are RAM-only, so their queued QoS 1/2 publications do not
survive a broker restart. The persistence layer is hook-based: a default POSIX
backend stores records as files under the directory given with -D (default
/var/lib/wolfmqtt on Linux or /private/var/lib/wolfmqtt on macOS). Embedded
targets can supply their own storage backend
through MqttBroker_SetPersistHooks().
The POSIX backend validates the store before using it and refuses to start
otherwise. The directory given with -D, and each namespace directory beneath
it, must be owned by the broker's effective user and must not be group- or
other-writable; no component of the path may be a symlink. A directory the
broker creates itself satisfies this (mode 0700), but one pre-created by a
script or installer inherits the ambient umask - under a relaxed umask such as
002 it comes out 0775 and is rejected. Create it explicitly:
mkdir -p /var/lib/wolfmqtt && chmod 0700 /var/lib/wolfmqtt
| Macro | Default | Description |
|---|---|---|
BROKER_MAX_PERSIST_SESSIONS |
64 | Dynamic-memory persistent sessions retained across restarts |
BROKER_MAX_OFFLINE_MSGS_PER_SUB |
32 | Offline queue depth per session |
WOLFMQTT_BROKER_PERSIST_SCHEMA_VER |
5 | On-disk record schema version |
Encryption at rest
Add --enable-broker-persist-encrypt (requires --enable-broker-persist) to wrap persisted records with wolfCrypt AES-GCM. The key is provided by a derive_key callback that real deployments install via MqttBroker_SetPersistHooks() before starting the broker.
For development and CI only, the CLI can link a fixed-pattern derive_key hook so the AES-GCM round-trip can be exercised without external key management. This is NOT a configure option -- define the macro through CFLAGS:
CFLAGS="-DWOLFMQTT_BROKER_PERSIST_ENCRYPT_DEV_KEY" \
./configure --enable-broker --enable-broker-persist --enable-broker-persist-encrypt
make
./src/mqtt_broker -p 1883 -D ./state -E dev
The dev key is a trivially-recoverable hard-coded pattern. Never define WOLFMQTT_BROKER_PERSIST_ENCRYPT_DEV_KEY in a production build, and never pass -E dev in production -- doing so substitutes the fixed key for real key management. Production builds omit the macro entirely, so the -E option and the dev hook are not present in the binary.
Testing
The repository ships an end-to-end broker test harness:
./scripts/broker.test
It builds the client examples (examples/pub-sub/mqtt-pub, examples/pub-sub/mqtt-sub) and mosquitto-based checks against the wolfMQTT broker, covering QoS flows, retained messages, wildcards, persistence round-trips, and AES-GCM encryption (when the dev-key hook is linked). Tests that depend on features not present in the current build are reported as SKIP.
The CONNECT-handler unit test (tests/test_broker_connect) is part of make check and exercises the broker packet path with a mock network layer.
Limitations
The wolfMQTT broker targets embedded and edge use cases. It is intentionally smaller in scope than full-featured server brokers such as Mosquitto or EMQX: there is no clustering, no bridging, no plugin/ACL framework, and no dynamic configuration reload. For large-scale or feature-rich deployments use a dedicated server broker; for a small, auditable, optionally-TLS broker that runs without threads or a heap, wolfMQTT is a good fit.