mercurioctl
Administers a running broker from the command line: clients and sessions, bans, retained messages, subscriptions, listeners, bridges and plugins, the log level, reloads and dynamic security.
It sends the commands of the admin API over MQTT 5, and prints the answers as tables, or as JSON for scripts.
Installing
Each release has .deb and .rpm packages and a .tar.gz for x86_64, aarch64 and armv7 Linux. The mercuriod packages recommend it; from downloaded files, install both at once:
# Debian, Ubuntu, Raspberry Pi OS sudo apt install ./mercuriod_*.deb ./mercurioctl_*.deb # Fedora, RHEL, openSUSE sudo dnf install ./mercuriod-*.rpm ./mercurioctl-*.rpm
Or with cargo: cargo install mercurioctl.
Connecting
Without connection options, mercurioctl connects through the broker's admin socket, /run/mercurio/admin.sock, when that file exists. There, as root or a member of the mercurio group, every command works without credentials. Without the socket, it connects to localhost:1883.
# On the broker's host mercurioctl client list # Another broker, over TLS; asks for the password mercurioctl -H broker.example.com -p 8883 --tls -u ops client list # Logging in with SCRAM-SHA-256, so the password never reaches the broker mercurioctl -H broker.example.com --auth-method scram-sha-256 -u ops client list # An admin socket somewhere else mercurioctl --unix /run/user/1000/mercurio/admin.sock broker info
Connection options go before the command. Each can also be set with an environment variable, which the option overrides:
| Option | Variable | Meaning | Default |
|---|---|---|---|
-H, --host, -p, --port | MERCURIOCTL_HOST, MERCURIOCTL_PORT | Broker over TCP | admin socket, else localhost:1883 |
--unix PATH | MERCURIOCTL_UNIX | Broker over a Unix socket | |
-u, --username, -P, --password | MERCURIOCTL_USERNAME, MERCURIOCTL_PASSWORD | Credentials | |
--tls, --cafile, --insecure | MERCURIOCTL_TLS, MERCURIOCTL_CAFILE, MERCURIOCTL_INSECURE | TLS, verified with the operating system's certificate store or only the --cafile CA, or not at all; --cafile and --insecure imply --tls | |
--cert PATH, --key PATH | MERCURIOCTL_CERT, MERCURIOCTL_KEY | Client certificate and its private key, both PEM; each needs the other, and they imply --tls | |
--ws, --ws-path PATH | MERCURIOCTL_WS, MERCURIOCTL_WS_PATH | MQTT over a WebSocket on the host and port, wss:// with TLS, at this path | /mqtt |
-i, --client-id | MERCURIOCTL_CLIENT_ID | Client id | assigned by the broker |
--timeout SECONDS | MERCURIOCTL_TIMEOUT | Longest wait for the answer, connecting included | 10 |
--json | Print the answer's data as JSON | tables |
MERCURIOCTL_TLS, MERCURIOCTL_INSECURE and MERCURIOCTL_WS take 1/0, yes/no, on/off or true/false. An empty variable counts as unset.
- Any connection option but
-iturns the admin socket default off:-u opsalone connects tolocalhost:1883. -uwithout-Pasks for the password when standard error is a terminal; elsewhere it logs in with the user name alone. A-Pon the command line is visible to other users inps, so scripts should setMERCURIOCTL_PASSWORDinstead.--jsonand--timeoutcan go anywhere on the line.- Through the admin socket, a user who may not use it gets Permission denied: run mercurioctl with
sudo, or join the group. - Over the network, the user needs permission for each command and for its answers: see Over the network.
Commands
Commands are mercurioctl <group> <verb>. What each one does and returns is in the command reference; --help after any of them lists its options.
| Command | Runs |
|---|---|
client list [--connected | --offline] [--prefix P] [--user U] [--address A] [--mount-point M] | listClients |
client show <clientid> [--mount-point M] | getClient |
client disconnect <clientid> [--mount-point M] [--no-will] [--reason-code C] | disconnectClient; C in decimal or 0x.. |
client delete-session <clientid> [--mount-point M] [--disconnect] [--yes] | deleteSession; asks for confirmation first, or needs --yes off a terminal |
ban add <clientid|username|address> <value> [--prefix] [--duration S] [--reason R] | addBan |
ban remove <type> <value> [--prefix] | removeBan |
ban list | listBans |
retained list [filter] | listRetained, # by default |
retained show <topic> | getRetained |
retained delete <filter> [--yes] | deleteRetained; a filter with + or # asks for confirmation first, or needs --yes off a terminal |
subscription list [--prefix P] | listSubscriptions |
subscription subscribers <filter> | getSubscribers |
broker info | getBrokerInfo |
broker config | getConfig |
broker log-level [level] | getLogLevel, or setLogLevel with a level |
broker reload | reload |
listener list, bridge list, plugin list | listListeners, listBridges, listPlugins |
Each mount point has its own client ids. When an id is on more than one, client show, disconnect and delete-session need --mount-point, "" for a listener without one; the broker's error lists the mount points to choose from.
The client, ban, retained and subscription lists also take --offset N and --count N.
# Clients connected from one network mercurioctl client list --connected --address 10.1.0.0/16 # Disconnect a client without publishing its will, then ban its address for an hour mercurioctl client disconnect sensor-7 --no-will mercurioctl ban add address 203.0.113.0/24 --duration 3600 --reason flooding # Retained messages under a retired prefix, then delete them mercurioctl retained list 'old/#' mercurioctl retained delete 'old/#' # Debug one module until the next restart mercurioctl broker log-level info,mercurio_server::session=debug
Dynamic security
mercurioctl dynsec runs the dynamic security commands under mosquitto_ctrl's names in kebab-case: createClient is dynsec create-client, addRoleACL is dynsec add-role-acl.
# A role that may publish under sensors/, a group with it, and a client in the group
mercurioctl dynsec create-role sensor
mercurioctl dynsec add-role-acl sensor publishClientSend 'sensors/#' allow
mercurioctl dynsec create-group sensors --role sensor
mercurioctl dynsec create-client s1 --group sensors
mercurioctl dynsec get-client s1
create-clientwithout--passwordasks for one twice. An empty one creates a client that can't log in untilset-client-password.- In scripts,
create-clientandset-client-passwordtake the password from the first line of a file with--password-file PATH, or of standard input with--password-stdin, which keeps it out ofps:pass show mqtt/s1 | mercurioctl dynsec set-client-password s1 --password-stdin. add-role-acltakes the ACL type, the topic,allowordeny, and an optional priority.add-client-role,add-group-roleandadd-group-clienttake an optional priority last too; run again on a role or group already held, they change its priority.modify-client,modify-groupandmodify-rolesend only the options given.--role,--groupand--client, repeated, replace the whole list, with priority -1;--no-roles,--no-groupsand--no-clientsempty it.modify-rolereplaces every ACL with its--acl TYPE TOPIC allow|deny PRIORITYoptions, or removes them all with--no-acls.modify-clientchanges the password only with--password,--password-fileor--password-stdin, and--clientid ""lets the client connect with any client id. For example,mercurioctl dynsec modify-role sensor --acl publishClientSend 'sensors/#' allow 0 --acl subscribePattern 'sensors/#' allow 0.- mosquitto_ctrl's
initismercuriod dynsec-init, which writes the file before the broker starts. - When dynamic security isn't enabled, nothing answers: mercurioctl waits for
--timeout, exits with status 2 and asks whether it is enabled.
Output
Lists print as tables, other answers as name: value lines. Times are in UTC, and empty values print as -:
$ mercurioctl client list CLIENT ID MOUNT POINT USERNAME CONNECTED ADDRESS SINCE SUBS QUEUED INFLIGHT DROPPED monitor - - true 127.0.0.1:41302 2026-11-10 20:39:05Z 1 0 0 0
- A list longer than what was shown ends with a line such as
1-100 of 250; --offset 100 shows the next ones. --jsonprints the answer'sdataas the broker sends it, with the field names and Unix times of the command reference, and nothing for commands without data.broker configalways prints JSON.- With
--json, a failure prints as one JSON object on standard error,{"error": "no client `x`", "code": "notFound"}, with the broker's error code, ornullwhen there is none. The exit status stays the same. Mistakes on the command line itself, which clap reports before--jsonis read, stay plain text. - A table cell holds one line, so
retained showprints a payload's line breaks as spaces.--jsonkeeps them:mercurioctl retained show status/door --json | jq -r .payload.
Shell completions
mercurioctl completions <shell> prints a completion script for bash, zsh, fish, elvish or powershell:
mercurioctl completions bash > ~/.local/share/bash-completion/completions/mercurioctl
mercurioctl completions zsh > "${fpath[1]}/_mercurioctl"
Exit status
| Status | Means |
|---|---|
0 | Success |
1 | The broker refused: the command failed, with its code in parentheses after the message, or access control refused the request or its answer topic. broker reload also exits 1 when any file failed to load, after printing the table. |
2 | The broker couldn't be reached, refused the login, closed the connection, or didn't answer within --timeout |
3 | Wrong usage, such as an unknown option, mismatched passwords, or a destructive command not confirmed |
Messages go to standard error, so scripts can read the output:
# Disconnect every client of a retired user
mercurioctl --json client list --user legacy --connected |
jq -r '.clients[] | [.clientid, .mountpoint // ""] | @tsv' |
while IFS=$'\t' read -r id mount; do
mercurioctl client disconnect "$id" --mount-point "$mount"
done