Configuration reference

Every option of mercuriod's TOML configuration file, with its default. Every option is optional. An option or table the broker does not know, such as a misspelled one, stops it from starting with an error that names it.

The broker reads /etc/mercurio/config.toml, or the file given with -c. A commented file with every option is provided as mercuriod/contrib/config.toml.

Checking a configuration

mercuriod --check does what a start does short of listening and opening storage: it reads the configuration and its include_dir, checks the settings that depend on each other, loads the password, ACL, dynamic security and JWT key files of [auth] and of every listener, fetches their JWT key sets, loads the TLS certificates and keys, compiles and initialises the WebAssembly plugins, and sets up every bridge: its routes, its remote addresses and its TLS files. It then exits with status 0. A problem makes it exit with status 1 and print the error a start would. It does not connect to PostgreSQL, bind ports or sockets, or contact bridges.

# As the broker's user, so files are read with its permissions
sudo -u mercurio mercuriod --check -c /etc/mercurio/config.toml && sudo systemctl restart mercuriod

Top level

OptionDefaultMeaning
include_dirDirectory whose *.toml files are merged into this one, in name order. Their tables extend this file's, their arrays ([[listeners]], [[bridges]]) are added to this file's, and their other values replace this file's.
# /etc/mercurio/config.toml
include_dir = "/etc/mercurio/conf.d"

# /etc/mercurio/conf.d/tenant1.toml adds a listener
[[listeners]]
port = 1885
mount_point = "tenant1/"

[server]

Main listener

OptionDefaultMeaning
host"127.0.0.1"Address to listen on. "0.0.0.0" for every IPv4 interface, "::" for IPv6.
port1883Port of the main MQTT listener.
bind_interfaceOnly accept connections arriving on this network interface, such as "eth0". Linux only.
websocket_portAlso serve MQTT over WebSocket on this port, with the main listener's TLS settings.
http_dirServe the files of this directory to plain HTTP requests on websocket_port.

Limits

OptionDefaultMeaning
max_connections10000Concurrent connections, across every listener. Further ones are closed right after being accepted.
max_packet_size1048576Largest packet accepted from a client, in bytes. Announced to MQTT 5 clients.
max_connect_packet_size65536Largest CONNECT accepted from a client, in bytes, and largest AUTH during enhanced authentication. These come before the client is authenticated, so they get less room than other packets; max_packet_size still caps it. A larger one is refused as Packet Too Large. On WebSocket listeners, a client may send twice this many bytes, framing included, before it is authenticated; past that, the connection is closed. At least 1.
connect_timeout10Seconds a new connection has, in all, to send its PROXY header, complete the TLS and WebSocket handshakes and send CONNECT, enhanced authentication included.
max_queued_messages10000Messages that may wait for one client, any QoS, before further ones are dropped.
max_queued_qos0_messages1000Queued messages beyond which QoS 0 messages are dropped.
max_queued_bytes104857600Payload bytes that may wait for one client before further messages are dropped.
shared_subscription_strategy"round_robin"How a shared subscription's group picks the member each message goes to: "round_robin", the member after the one that took the last; "random"; "sticky", the member that took the last one, for as long as it is connected and its queue has room; or "hash", a member picked by the publisher's client id, so the messages of one publisher reach one member in order while the group stays the same. With any strategy a connected member whose queue takes the message comes before the others, and a disconnected member only gets it when no connected one can. Messages the broker publishes itself, such as wills, go round robin under "hash".
shared_subscription_redelivery"session_end"When the messages a member of a shared subscription was given and hasn't processed go to another member of its group: those still queued for it, and those at QoS 1 it was sent and hasn't acknowledged. With "session_end" they go when the member's session ends, as its expiry passes, a clean start replaces it or it is deleted; until then they wait for the member, and a member whose session ends with its connection hands them over as it disconnects. With "disconnect" they also go as soon as the member loses its connection, to the members connected then; what none of them takes waits for the member. A QoS 2 message already sent stays with the member, as MQTT requires. A QoS 1 message the member processed but did not acknowledge before leaving reaches the application a second time, through the other member, so applications on shared subscriptions should handle duplicates. What no member takes when a session ends is lost and counted as no_share_member.
queue_overflow"drop_newest"Which message a full queue drops: "drop_newest", the one arriving, so a client that falls behind gets the start of what it missed; or "drop_oldest", the oldest waiting of the same kind (QoS 0, or QoS 1 and 2), as many as it takes, so it gets the latest. A QoS 0 message finding only QoS 1 and 2 ones waiting is still dropped, and a shared subscription's member pushes nothing out: the message goes to another member. Dropped messages count under publish/messages/dropped/queue_full_qos0 and queue_full either way.
receive_maximum65535QoS 2 messages an MQTT 5 client may have awaiting PUBREL at once.
max_inflight64QoS 1 and 2 messages sent to one client and not yet acknowledged, at most, whatever Receive Maximum the client announces. At least 1.
max_topic_alias10Highest topic alias MQTT 5 clients may use; 0 disables aliases.
max_keepaliveLongest keep-alive, in seconds, MQTT 5 clients may use. Clients asking for more, or for none, are told to use this one.
max_qos2Highest QoS clients may use. Subscriptions above it are granted this QoS; publishing above it closes the connection.
retain_availabletrueWith false, retained messages are refused, and so are wills that ask to be retained.

Client ids and sessions

OptionDefaultMeaning
allow_zero_length_clientidtrueAccept clients that send an empty client id and assign them one.
auto_id_prefix""Prefix of the client ids the broker assigns.
clientid_prefixes[]When not empty, only client ids starting with one of these are accepted; others are refused as not authorized.
persistent_client_expirationneverSeconds the session of an MQTT 3.x client connected without clean session outlives its connection. MQTT 5 clients choose their own expiry.
max_session_expiry604800 (a week)Most seconds any session may outlive its connection. MQTT 5 clients asking for longer, or for no expiry, are told to use this; it also bounds persistent_client_expiration, so MQTT 3.x sessions end a week after their client left too. "unlimited" keeps sessions for as long as their clients ask, forever included; each stored session takes memory and storage, so keep a bound where clients aren't trusted.
max_sessionsunboundedSessions that outlive their connection the broker may hold, connected or not. A client whose CONNECT would start one more is refused with Quota Exceeded (Server Unavailable for MQTT 3.x); resuming an existing session never is. Sessions that end with their connection, Clean Session in MQTT 3.x or a zero Session Expiry Interval in MQTT 5, don't count, as max_connections bounds them; a broker at its limit still serves such clients. Sessions restored at startup count, even beyond the limit.
max_retained_messagesunboundedTopics that may hold a retained message at once, the broker's own $SYS topics not counted. Retaining a message on another topic is refused with Quota Exceeded; updating or clearing a retained topic is always allowed.
max_publish_rate, publish_rate_burstunlimited, max_publish_ratePUBLISH packets per second one client may send on average, and how many it may send at once after a quiet spell. A client going faster isn't read from until its rate allows another message, so TCP's flow control slows it down to the rate; nothing it sent is dropped or refused, and its keep-alive doesn't run out meanwhile. In-process clients, such as bridges, and clients of the admin socket are not limited.
max_publish_bytes_rate, publish_bytes_burstunlimited, max_publish_bytes_rateThe same for the payload bytes of the PUBLISH packets one client sends.
publish_rate_graceunsetSeconds a client may stay over max_publish_rate or max_publish_bytes_rate before it is disconnected with Message Rate Too High; MQTT 3.x clients are just disconnected. A client is over its rate from the first PUBLISH the rate holds back until the broker, reading from it again, finds nothing waiting. Unset, clients are only slowed down. Needs one of the rates.
login_failure_attempts, login_failure_window, login_failure_ban10, 60, 300A client address that fails to log in login_failure_attempts times within login_failure_window seconds is banned for login_failure_ban seconds. Wrong passwords, missing credentials where they are required and failed enhanced authentication count. Addresses count as for max_connections_per_ip, IPv6 by /64, so the ban covers the block. It is an ordinary address ban with an expiry and the reason automatic: …: mercurioctl ban list shows it, and removing it lets the address in again at once. Loopback addresses, in-process clients and clients of the admin socket are never banned. Behind a proxy, set proxy_protocol so the clients' own addresses count, not the proxy's. login_failure_attempts = 0 turns it off. Failures are tracked for 16384 addresses at most, so a client cycling through addresses can't exhaust memory.
max_connections_per_ipunboundedConnections one client address may hold at once, counted from the moment they are accepted, before any TLS or WebSocket handshake; further ones are closed. An IPv6 client counts as its /64 block, and an IPv4 client reaching an IPv6 socket as its IPv4 address. Behind a proxy sending PROXY headers, the address the header gives is counted, so set proxy_from too. Unbounded by default, as carrier NAT puts many devices behind one address.
plugin_timeout5Seconds one call of a plugin hook may take, for the built-in password file, ACL file, HTTP and dynamic security checks as for [[plugins]]. A slower call denies, or drops the message. The broker refuses to start when [auth.http] timeout_seconds or a plugin's timeout_ms is longer.
plugin_tick_interval1Seconds between calls of the plugins' on_tick.

Delivery

OptionDefaultMeaning
upgrade_outgoing_qosfalseSend messages at the QoS of the subscription even when published with a lower one. Goes beyond the MQTT specification.
max_subscriptions10000Subscriptions one session may hold. Further ones are refused with Quota Exceeded; replacing one it holds is allowed.
max_topic_filter_length1024Longest topic filter, in bytes, a SUBSCRIBE may name. Longer ones are refused with Topic Filter Invalid (a failure for MQTT 3.x clients). Each filter is kept in memory and storage for as long as its session.
check_retain_sourcefalseSend a retained message to new subscribers only while the client that published it may still publish to its topic.
send_topic_aliasesfalseReplace repeated topics with aliases for MQTT 5 clients that accept them. Off by default because some client libraries announce alias support they don't implement.
response_informationTopic prefix given to MQTT 5 clients that request Response Information; %c is replaced by the client id, as in "responses/%c".
sys_interval10Seconds between updates of the $SYS statistics; 0 disables them.

[server.redirect]

Sends every client to another server instead of serving it. MQTT 5 clients are told where to go; MQTT 3.x clients only learn that this server is unavailable.

OptionDefaultMeaning
server_referencerequiredWhere to go, such as "broker2.example.com:1883".
permanentfalseServer Moved when true, Use Another Server otherwise.

[server.tls]

TLS for the main listener, and for websocket_port. [listeners.tls] takes the same options. See TLS.

OptionDefaultMeaning
enabledfalseServe TLS instead of plain TCP.
cert_pathCertificate chain, PEM.
key_pathPrivate key, PEM: PKCS#1, PKCS#8 or SEC1.
ca_pathRequire clients to present a certificate signed by this CA.
require_certificatetrueWhen false, clients without a certificate connect and log in by user name and password; a certificate that is presented must still be valid. Needs ca_path.
crl_pathRefuse client certificates revoked in these CRLs, PEM. Every certificate of the client's chain needs a CRL from its issuer here, or the client is refused. Needs ca_path.
crl_end_entity_onlyfalseCheck only the client's own certificate against crl_path, not the intermediate CAs that issued it. Needs crl_path.
crl_allow_unknownfalseAccept certificates whose issuer has no CRL in crl_path, instead of refusing them. Needs crl_path.
identity_as_usernamefalseUse the client certificate's Common Name as the user name. Needs ca_path.
subject_as_usernamefalseUse the whole certificate subject as the user name, in RFC 2253 form. Needs ca_path.
min_version"1.2"Oldest TLS version accepted: "1.2" or "1.3".

[logging]

OptionDefaultMeaning
level"info"trace, debug, info, warn or error, or per module, as in "info,mercurio_server::session=warn". An empty or invalid level means info, with a warning in the log. setLogLevel changes it until the broker restarts.
fileAppend logs to this file instead of standard output.
syslogfalseSend logs to the local syslog daemon through /dev/log.
connection_messagestrueLog clients connecting and disconnecting. With false, a level naming their target, mercurio::connections, still turns them on.

[storage]

OptionDefaultMeaning
backend"memory""memory", lost on restart, "sqlite" or "postgres".
pathThe SQLite database file, with backend = "sqlite".
urlThe PostgreSQL database, such as "postgres://mercurio:password@localhost/mercurio", with backend = "postgres". PostgreSQL 12 or later, connected to directly, without PgBouncer. The broker creates its tables on start. Connections to another host are encrypted, and fail if the server has TLS off; a server on the same host, over loopback or a Unix socket, may decline encryption unless ca_path is set. ?sslmode=require, verify-ca and verify-full insist on encryption and all verify the server's certificate and host name. ?sslmode=prefer or ?sslmode=disable allow plaintext, with a warning in the log for another host. The log says whether the connection is encrypted.
ca_pathMozilla's root certificatesPEM file of the certificate authority that signed the PostgreSQL server's certificate, such as a cloud provider's bundle. The certificate and the host name are always verified. Setting it requires encryption, even to a server on the same host.
standbyfalseWith PostgreSQL, when another broker is using the database, wait and take over once it stops, instead of exiting. See Standby brokers.
pool_size16With PostgreSQL, the connections the broker keeps to the database. Each broker, standbys included, opens one more, which holds the database, so the server's max_connections must allow pool_size + 1 for each. Every client's writes and every lookup of retained messages share the pool.
pool_timeout5With PostgreSQL, the seconds a write waits for a free connection once all are in use. A write that times out is tried again, and is not lost.

[auth]

See Authentication & access. [listeners.auth] takes the same options.

OptionDefaultMeaning
enabledfalseRequire clients to authenticate. Needed by password_file, dynamic_security, user_url and [auth.jwt]: the broker refuses to start when one of them is set and this is false. acl_file and acl_url work without it.
allow_anonymousfalseWith authentication enabled, still accept clients that send no user name.
password_fileUser names and password hashes.
acl_fileTopic access rules, in mosquitto's format.
dynamic_securityDynamic security state file; replaces password_file and acl_file. Only in [auth].
scram_secret_fileFile with 32 bytes of the secret that the SCRAM salts of unknown users come from. Created with random bytes and mode 0600 when missing, and the broker refuses to start when it has another length. Set it so the salts, and so the list of users, stay hidden across restarts. --check does not create it.
scram_require_channel_bindingfalseRefuse SCRAM-SHA-256 without channel binding on TLS 1.3 connections, so clients there must use SCRAM-SHA-256-PLUS. Plain TCP, Unix sockets, TLS 1.2 and connections behind a TLS-terminating proxy cannot bind and are not affected, and plain user name and password CONNECTs on the same listener are still accepted. A refused client counts as a failed login. See SCRAM-SHA-256-PLUS.

[auth.http]

OptionDefaultMeaning
user_urlEndpoint checking logins; replaces password_file.
acl_urlEndpoint checking topic access; replaces acl_file.
superuser_urlEndpoint naming superusers, whom acl_url doesn't restrict and who may administer the broker over $CONTROL. Needs enabled = true.
timeout_seconds5Longest wait for an answer; a timeout denies. No longer than [server] plugin_timeout.
cache_seconds30How long topic answers are reused.
ca_pathPEM file of the CA certificates that https:// endpoints' certificates must chain to, in place of the bundled roots.
cert_pathPEM certificate chain presented to https:// endpoints that ask for one. Needs key_path.
key_pathPEM private key of cert_path.

[auth.jwt]

Clients log in with a JSON Web Token as their password. See JSON Web Tokens. Needs enabled = true; can't be combined with dynamic_security.

OptionDefaultMeaning
secret_fileFile holding the shared secret of HS256, HS384 and HS512, at least 32 bytes; one trailing newline is removed. Reread on reload.
public_key_filePEM public key of RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 or EdDSA. Reread on reload.
jwks_urlJSON Web Key Set of the issuer, fetched at startup. https:// with a publicly trusted certificate, or http:// to a loopback address; redirects are not followed. Not with HMAC algorithms. Exactly one of secret_file, public_key_file and jwks_url is required.
algorithmsRequired. The algorithms tokens may be signed with, such as ["RS256"], all taking the same kind of key. The token's header never chooses.
issuerWhen set, tokens must carry this iss.
audienceWhen set, tokens must carry this aud.
username_claim"sub"Claim holding the user name, which must equal the CONNECT user name.
leeway_seconds30Clock skew allowed on exp and nbf.
disconnect_on_expirytrueDisconnect a client with Not Authorized once its token expires, checked every [server] plugin_tick_interval.
jwks_refresh_seconds300Least time between two fetches of jwks_url, made when a token names a key the set lacks. At least 10, as any client can name a missing key.

[admin]

The admin socket, through which local administrators run every command without credentials. See Administration. Changes need a restart.

OptionDefaultMeaning
socket"/run/mercurio/admin.sock"Path of the Unix socket; "" turns it off. Every directory on the path must be writable only by root or the broker's user. Linux only; elsewhere the section is ignored.
group"mercurio"Group whose members may use the socket, besides root and the broker's user, as its members were when the broker started. "" for nobody else.

[[listeners]]

Each entry adds a listener. See Listeners.

OptionDefaultMeaning
host"127.0.0.1"Address to listen on.
portTCP port. Either this or path.
pathUnix domain socket. Either this or port.
groupWith path, the group the socket belongs to. The broker's user must be a member, or root. Linux only.
mode"0660" with group, "0600" withoutWith path, the socket's permission bits in octal. Clients need write permission to connect, so the default lets only the broker's user, and the members of group, in; "0666" lets in every local user. The socket is created owner-only and opened up once its group is set. A socket left by a broker that stopped is replaced; one another process serves stops the broker with an error. Linux only.
protocol"mqtt""mqtt" or "websocket".
http_dirOnly with "websocket": serve this directory's files to plain HTTP requests.
healthfalseOnly with "websocket": answer GET /healthz with 200 while the broker serves. See Health and metrics.
metricsfalseOnly with "websocket": answer GET /metrics with the statistics in the Prometheus text format. Anyone who reaches the listener learns how loaded the broker is, so use a listener of its own on a loopback address.
allowed_originsanyOnly with "websocket": the origins of the web pages that may connect, such as ["https://app.example.com"]. An upgrade with another Origin is refused with 403. Clients other than browsers send no Origin and are not affected.
proxy_protocolfalseConnections start with a PROXY protocol header, v1 or v2.
proxy_fromWith proxy_protocol on a TCP listener, required: the addresses or CIDR blocks of the proxies allowed to send the header, such as ["10.0.0.0/8"], or ["0.0.0.0/0", "::/0"] for any peer. Connections from elsewhere are closed, as their header could claim any address.
mount_pointPrefixed to every topic the listener's clients use. Must end with / and hold no + or #.
max_connectionsConcurrent connections on this listener, on top of the broker-wide limit.
bind_interfaceOnly accept connections arriving on this interface. Linux only.
[listeners.tls]Same options as [server.tls].
pluginsallNames of the [[plugins]] this listener's clients go through, such as ["deny"]. An unknown name is an error.
[listeners.auth]Same options as [auth], except dynamic_security. Replaces [auth] for this listener.

[[bridges]]

Each entry connects to another broker. See Bridges.

OptionDefaultMeaning
namerequiredNames the bridge in logs and in its default client id.
host, portport 1883The remote broker.
addressesRemote brokers as "host:port", tried in turn; replaces host and port.
protocol_version"mqttv5""mqttv5" or "mqttv311".
client_id"mercurio-bridge-<name>"Client id on both brokers.
username, passwordCredentials for the remote broker.
keepalive60Keep-alive with the remote broker, in seconds.
clean_startfalseStart a new session on the remote broker at each connection instead of resuming the one kept there.
session_expiry86400Seconds the remote broker keeps the session, and the messages for in topics, after the link drops.
notificationstruePublish the bridge's state, retained, to notification_topic.
notification_topic"$SYS/broker/connection/<client id>/state"Where the state goes.
notifications_local_onlytrueWith false, publish the state on the remote broker too, which publishes 0 through the bridge's will when the link drops.
[bridges.tls]enabled, ca_path, and cert_path with key_path for mutual TLS.

[[bridges.topics]]

OptionDefaultMeaning
patternrequiredTopic filter to forward.
direction"out""out" to the remote broker, "in" from it, or "both".
qos0QoS of the bridge's subscriptions.
local_prefix""Where pattern sits on this broker.
remote_prefix""Where pattern sits on the remote broker.

[[plugins]]

Each entry loads a WebAssembly plugin, run in the order written, after the built-in authentication and access control. See WebAssembly plugins. Two plugins can't have the same name.

OptionDefaultMeaning
pathrequiredThe component, a .wasm file. SIGHUP rereads it.
namefile name without .wasmNames the plugin in logs, in $CONTROL/<name>/... and in listener plugins. It can't be empty, contain /, + or #, or be the name of a built-in plugin: dynamic-security, password-file, http-auth, acl-file, plain or credentials.
config{}Table of strings passed to the plugin's init.
allow_http[]Hosts it may send HTTP requests to, as "host" (port 443) or "host:port". The scheme isn't checked, so a plain-HTTP endpoint on the default port needs "host:80".
allow_sockets[]Addresses it may connect sockets to, as "address:port". It can never listen.
instancesnumber of CPUsCopies that run calls at once.
timeout_ms[server] plugin_timeoutLongest one call, or init, may run. No longer than plugin_timeout, which bounds every hook call too.
memory_mb64Linear memory each copy may use, all its memories together.

Only the file at path is reread on SIGHUP; other changes need a restart.