msh CLI Reference
msh is the client CLI for Mezite. It lets an end user log in
to a cluster, open SSH sessions, copy files, request access, manage their
device-trust status, and join or moderate other sessions.
login
Authenticate to a Mezite cluster and receive short-lived SSH
certificates. --user and --password are required
for credential-style auth (local username/password and LDAP). Browser-mediated
SSO connectors (OIDC, SAML, GitHub) are selected with --auth=<connector> and complete the login via the proxy callback.
# Local username + password
msh login --proxy=mezite.example.com:3080 \
--user=admin --password="$MEZITE_PASSWORD"
# LDAP / Active Directory (uses the same --user / --password)
msh login --proxy=mezite.example.com:3080 \
--auth=corp-ldap --user=alice --password="$LDAP_PASSWORD"
# SSO (OIDC / SAML / GitHub) — opens the browser
msh login --proxy=mezite.example.com:3080 --auth=okta
# Custom certificate TTL (default 12h, hard cap 24h)
msh login --proxy=mezite.example.com:3080 \
--user=alice --password="$MEZITE_PASSWORD" --ttl=30m Identity-file export (--out)
Pass --out <path> to write the issued SSH key pair and
certificate to disk in OpenSSH identity-file format instead of stashing them
in the per-profile cache. This is what CI runners, scheduled jobs, and Machine-ID-style
helpers consume.
msh login --proxy=mezite.example.com:443 \
--auth=ci-machine-id \
--user=ci-bot --password="$CI_BOT_PASSWORD" \
--out=/tmp/mezite-identity
# Use it with native ssh
ssh -i /tmp/mezite-identity ubuntu@web-01.mezite Hardware-key login (--hardware-key)
The --hardware-key flag is recognized by
msh login, and roles with
require_session_mfa: hardware-key are honoured by the proxy and
auth service. However,
real PIV hardware-key signing is not yet implemented
— invoking the flag against a hardware token currently fails with
no hardware key implementation available; real PIV support not yet
built. The flag, RBAC policy hooks, and certificate extension are in place
so that integration can land without churning the public surface; treat
the feature itself as unimplemented today.
ssh
Open an interactive SSH session, or run a one-off remote command, against a registered node.
# Connect by hostname
msh ssh ubuntu@web-01
msh ssh --login=ubuntu web-01
# Run a one-off command
msh ssh web-01 -- uptime
# Pre-approve a session-MFA challenge in the same invocation
msh ssh --mfa-code=123456 web-01
# Attach a justification (logged in the audit event)
msh ssh --reason="incident IR-204" web-01
# Invite extra participants into the session (for moderated roles)
msh ssh --invited=alice,bob web-01
# Cross-cluster: target a node in a trusted leaf cluster
msh ssh --cluster=production web-01 scp
Copy files between a workstation and a node through the Mezite proxy. Every transfer is authenticated with your certificate, logged in the audit trail, and — under a role with file-transfer moderation — gated by a per-file approval workflow.
# Upload
msh scp ./deploy.tar.gz ubuntu@web-01:/tmp/
# Download
msh scp ubuntu@web-01:/var/log/app.log ./
# Recursive
msh scp -r ./config/ ubuntu@web-01:/etc/app/ msh scp does not support remote-to-remote copies; one side must
always be local.
Transfer approval (moderated roles)
When the requester's role triggers SFTP moderation, the transfer pauses on the proxy until a designated moderator approves it. The moderator uses:
# Approve a pending transfer
msh approve-transfer --session=<session-id> --transfer=<transfer-id>
# Deny it
msh deny-transfer --session=<session-id> --transfer=<transfer-id> ls
List nodes visible from the active profile. --filter is repeatable
and AND-combines key=value matches against node labels.
msh ls
msh ls --filter=env=production
msh ls --filter=env=production --filter=team=platform
msh ls --all # include trusted leaf clusters app
Reach internal applications published through the proxy. HTTP apps are opened in a browser at their public address; TCP apps (databases, caches, custom daemons) are reached by opening a local port that tunnels through the proxy. See the Application Access guide for the full picture.
# List the apps your roles let you reach
msh app ls
# HTTP app: prints the public address to open in a browser
msh app login grafana
# TCP app: open a local listener that tunnels to the app
msh app login redis-internal --port=6379
# then point a client (e.g. redis-cli) at 127.0.0.1:6379; Ctrl-C to stop
# Bind the tunnel to a different local address
msh app login redis-internal --port=6379 --listen=0.0.0.0
# Apps whose role requires per-session MFA: supply or be prompted for a code
msh app login redis-internal --port=6379 --mfa-code=123456 msh app ls shows an AUTH column flagging any
app that requires extra per-session proof (mfa or a trusted
device). Apps you can't reach do not appear in the list.
config
Generate ~/.ssh/config entries for every node visible from the
active profile. Each entry pins the Mezite host CA via
UserKnownHostsFile ~/.mezite/known_hosts and routes through msh proxy ssh, so native ssh,
rsync, Ansible, and editor SSH integrations all benefit
from certificate auth and audit.
msh config # print to stdout
msh config --append # append to ~/.ssh/config
msh config --cluster=production # change the Host alias suffix
msh config --login=deploy # remote login for trusted-cluster entries --login sets the remote login used in the generated
trusted-cluster entries; it defaults to the first principal on your
certificate.
proxy ssh
Use msh proxy ssh as an OpenSSH ProxyCommand
to route native ssh(1) through the Mezite proxy. The argument
takes a single [user@]host:port token, so OpenSSH's %r@%h:%p substitution drops in directly.
# Typical entry generated by 'msh config'
# ProxyCommand msh proxy ssh %r@%h:%p
# Manual one-off
ssh -o 'ProxyCommand=msh proxy ssh %r@%h:%p' ubuntu@web-01.mezite
# Direct invocation
msh proxy ssh ubuntu@web-01:22 play
Replay a recorded session in the terminal. Speed is configurable.
msh play <session-id>
msh play --speed=2.0 <session-id> sessions / recordings
Inspect recorded sessions. msh sessions ls and
msh recordings ls are aliases; both surface the same list.
msh sessions ls
msh recordings ls join
Join an active or pending session in one of three modes. For roles
configured with
require_session_join, the session is held open at start
time until the required moderators have joined; this command is how a
moderator answers that hold.
-
observer(default) — read-only. See the terminal; cannot type or approve transfers. peer— interactive. See the terminal and send input.-
moderator— supervisor. Present for moderated sessions; may approve or deny transfer requests. Requires ajoin_sessionsallow rule permittingmoderatorfor this session's owner.
# Observe a session (default mode)
msh join <session-id>
msh join --mode=observer <session-id>
# Attach in interactive mode (if the role allows)
msh join --mode=peer <session-id>
# Attach as a moderator
msh join --mode=moderator <session-id> While attached, press Ctrl-C to disconnect. A moderator may press t to terminate the source session.
request
Create, list, and cancel access requests. A request can ask for whole
roles (--roles), for access to specific SSH nodes (--resource), or both. At least one of the two must be set.
# Request one or more roles for the grant window
msh request create --roles=admin --reason="Deploy hotfix"
# Request a single node instead — approval grants access to that node only
msh request create --resource=ssh-node:web-01 --reason="incident XYZ-123"
# --resource is repeatable
msh request create \
--resource=ssh-node:web-01 \
--resource=ssh-node:web-02 \
--reason="debug"
# A node in a trusted cluster: ssh-node:<cluster>/<name>
msh request create --resource=ssh-node:leaf-prod/web-01 --reason="cross-cluster check"
# Roles and resources together
msh request create --roles=ssh-prod --resource=ssh-node:web-01 --reason="scoped deploy"
msh request ls
msh request ls --state=pending
msh request cancel <request-id> --duration sets how long the grant should last (e.g.
2h); the cluster caps it — see
MEZITE_ACCESS_REQUEST_MAX_DURATION on the
Configuration page.
--urgency takes normal, high, or
critical. A resource request grants access to the named
node(s) for the request's duration and nothing broader.
device
Manage the device-trust enrollment of the current machine. Enrollment is
required before you can log in under any role with
device_trust_mode: required — the SSH gate fails closed, so
an un-enrolled device is refused rather than quietly downgraded. Under
device_trust_mode: optional the login still succeeds, but
the issued certificate carries no device ID extension.
msh device serial # print this device's hardware serial
msh device enroll # enroll this device as a trusted device
msh device status # report whether this device is enrolled and trusted bench
Open many real SSH sessions to a node and report how the access path performs under concurrency. Each iteration runs the full path — dial the proxy, complete both SSH handshakes, open a session, run a command — so it exercises audit and session-recording load as well as the network. It reports connect / handshake / exec latency percentiles (p50/p95/p99), throughput, and the error rate.
# Burst: 250 sessions, 50 at a time
msh bench -n 250 -c 50 web-1
# Soak: run for 10 minutes at 20 concurrent sessions
msh bench --duration 10m -c 20 web-1
# Gate a run against service-level objectives — non-zero exit on violation
msh bench -n 200 -c 25 \
--max-error-rate 0.01 --connect-p95-ms 750 --exec-p95-ms 500 web-1
# Machine-readable output
msh bench -n 100 --format=json web-1
Choose the load shape with --requests/-n (a
fixed number of sessions, default 100) or --duration (run
for a fixed time); --duration wins if both are set.
--command overrides what each session runs,
--login the remote username, and --proxy the
proxy address from the current login.
status / logout
msh status # show cluster, user, roles, cert expiry
msh logout # remove local certificates and end the session version
Print the build stamp of the binary you are running. Every Mezite
binary — msh, mezctl, mezd and
mezhub — accepts the same subcommand and prints the same
shape, so it is the first thing to paste into a bug report.
msh version
# Version: 0.2.24
# Git commit: 9344e36a
# Build date: 2026-07-29T18:22:04Z
msh version --format=json
# {
# "version": "0.2.24",
# "git_commit": "9344e36a",
# "build_date": "2026-07-29T18:22:04Z"
# }
These values are stamped into the binary when it is built. A binary you
compiled yourself reports its version as dev, and any field
the build did not stamp renders as unknown. That is
deliberately visible rather than hidden: an unstamped build should be
obvious in the field instead of looking like a released one.
Client version compatibility
A cluster publishes the client version it expects, and the oldest client
version it still supports. msh login reads that policy and
tells you if the binary in your hand is behind it.
- Older than the version the cluster expects — a single informational line on stderr. Everything keeps working; upgrade when convenient.
- Older than the minimum the cluster supports — a warning on stderr. Commands may start failing against this cluster, so treat it as a real to-do.
- Newer than the cluster — nothing is printed.
msh statusstill shows both versions. This is a normal state mid-upgrade and there is never a reason to downgrade a client.
The check never blocks a command and never changes what a command does. If it cannot reach the cluster, or the answer takes more than a moment, it says nothing at all — being offline must not stand between you and a node. The answer is cached in the profile directory and refreshed at most every four hours, plus on every login, so it costs nothing on the interactive path.
A client built from source reports its version as dev, which
cannot be compared to a release version, so no comparison is attempted
and no message is printed.
msh status
# Cluster: mezite.example.com
# Proxy: mezite.example.com:3080
# User: alice
# Roles: access
# Valid until: 2026-07-30 08:14:02 BST (11h58m remaining)
# Client: 0.2.20 (cluster expects 0.2.24)
# Skip the comparison for a single command
msh status --no-update-check
# Skip it for every command in this environment
export MEZITE_TOOLS_VERSION=off
Use MEZITE_TOOLS_VERSION=off where the client binary is owned
by something else — a package manager, a container image, a
configuration-management system — and the version is not yours to change.
Operators can also switch the policy off cluster-wide with
client_tools.mode: disabled; see
Client Tools.
msh does not update itself. Upgrading means installing a new
binary the same way you installed the first one — see
Installation.
Global flags
msh has exactly one persistent flag, accepted by every
subcommand:
-
--no-update-check— Do not compare this client's version against the cluster's policy.
The following look global but are per-command — check
msh <command> --help before assuming a command takes one:
-
--insecure— Skip server certificate verification. Dev-only. Accepted bylogin,ls,play,app login,app ls, and therequestsubcommands. -
--cluster <name>— Target a trusted cluster. Accepted byssh,scp,proxy ssh, andconfig. -
--proxy <host:port>— Proxy address. Set at login time onmsh login;msh benchtakes it as an override. Other commands read the address from the active profile.