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=30mIdentity-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.meziteHardware-key login (--hardware-key)
The --hardware-key flag is recognized bymsh login, and roles withrequire_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 withno 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-01scp
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 clustersapp
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 theApplication 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=123456msh app ls shows an AUTH column flagging any app that requires extra per-session proof (mfa or a trusteddevice). 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 viaUserKnownHostsFile ~/.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 ProxyCommandto 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:22play
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 andmsh recordings ls are aliases; both surface the same list.
msh sessions ls
msh recordings lsjoin
Join an active or pending session in one of three modes. For roles configured withrequire_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 — seeMEZITE_ACCESS_REQUEST_MAX_DURATION on theConfiguration page.--urgency takes normal, high, orcritical. 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 withdevice_trust_mode: required — the SSH gate fails closed, so an un-enrolled device is refused rather than quietly downgraded. Underdevice_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 trustedbench
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-1Choose 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 sessionversion
Print the build stamp of the binary you are running. Every Mezite binary — msh, mezctl, mezd andmezhub — 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=offUse 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 withclient_tools.mode: disabled; seeClient Tools.
msh does not update itself. Upgrading means installing a new binary the same way you installed the first one — seeInstallation.
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 — checkmsh <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.