SSH Access
Mezite replaces static SSH keys with short-lived certificates issued on demand. Every connection is authenticated via a User CA and Host CA certificate pair, authorized against RBAC policies, routed through the proxy, and recorded for audit. No static keys are ever stored on servers or distributed to users.
How SSH Works Through Mezite
Traditional SSH relies on distributing public keys to ~/.ssh/authorized_keys
on every server. Mezite eliminates this entirely with certificate-based authentication:
- User CA — The Mezite auth service acts as a certificate authority. When a user logs in (via password, SSO, or MFA), Mezite issues a short-lived SSH certificate signed by the User CA. This certificate encodes the user's identity, allowed logins, and an expiration time.
- Host CA — Each node agent receives a host certificate signed by the Host CA. Clients trust the Host CA, so there are no "unknown host" prompts and no TOFU (trust-on-first-use) vulnerabilities.
- No static keys — Certificates expire automatically (typically within hours). There are no long-lived keys to rotate, revoke, or audit.
When you run msh ssh, the following sequence occurs:
- Authentication —
mshpresents your user certificate (obtained duringmsh login) to the Mezite proxy on port3023. - Authorization — The proxy validates the certificate against the auth service. RBAC enforcement happens at session setup on the agent side using the role traits embedded in your user certificate.
- Routing — The proxy locates the target node via its reverse
tunnel connection (established by
mezdon port3024). - Session establishment — The proxy forwards the SSH session through the reverse tunnel to the agent. The agent itself terminates the SSH connection — it is the sshd for the node.
- Recording — The agent captures the terminal I/O stream at the PTY level and uploads it to the auth service for later playback.
workstation Mezite proxy target node
| | |
|-- msh ssh --login=user node ----->| |
| (user cert + request) | |
| |-- reverse tunnel ------->|
| | (proxied SSH session) |
| | |
|<-- interactive session -->|<-- agent SSH session --->|
| | (recorded by agent) | Prerequisites
-
A running Mezite cluster (
mezhubwith auth and proxy services enabled). - The msh client CLI installed on your workstation.
- The
mezdbinary available on the target node. -
Network connectivity from the target node to the Mezite proxy on port
3024(reverse tunnel).
Agent Setup
Generate a Join Token
Before a node can join the cluster, you need a one-time join token.
Generate one with
mezctl:
# Generate a token valid for 1 hour
mezctl tokens create --roles=node --ttl=1h
# Output:
# Token created: a1b2c3d4e5f6... (roles: node, expires: 2026-03-24T15:00:00Z) Install and Configure the Agent
On the target node, install mezd and configure it to connect
back to the Mezite proxy via a reverse tunnel.
# Download the signed agent binary (Linux amd64)
curl -fsSL https://releases.mezite.com/latest/mezite-linux-amd64.tar.gz \
| tar -xz -C /usr/local/bin/ mezd
Configure the agent via environment variables (e.g. in /etc/mezite/agent.env):
MEZITE_AUTH_ADDR=mezite.example.com:3025
MEZITE_PROXY_ADDR=mezite.example.com:3024
# Join token (used once for initial registration)
MEZITE_JOIN_TOKEN=a1b2c3d4e5f6...
# Node metadata
MEZITE_NODE_NAME=web-server-01
MEZITE_NODE_LABELS=env=production,team=platform,os=ubuntu Start the Agent (Reverse Tunnel)
The agent establishes a persistent reverse tunnel to the Mezite proxy on
port
3024. This tunnel is how the proxy routes SSH sessions to
the node, even if the node is behind a firewall or NAT.
# Start with systemd
sudo systemctl enable mezd
sudo systemctl start mezd
# Verify the agent registered
mezctl nodes ls
# HOSTNAME TYPE STATUS LABELS VERSION
# web-server-01 node online env=production,os=ubuntu,team=platform 0.1.0 Using msh ssh
Log In and List Nodes
# Log in to Mezite (obtain user certificate from the User CA)
msh login --proxy=mezite.example.com:3080 --user=alice
# List nodes registered with the cluster
msh ls
# HOSTNAME ROLE STATUS LABELS VERSION
# web-server-01 node online env=production,os=ubuntu,team=platform 0.1.0
# web-server-02 node online env=production,os=ubuntu,team=platform 0.1.0
# staging-01 node online env=staging,os=ubuntu,team=platform 0.1.0 Connect by Name
# Connect to a specific node
msh ssh --login=ubuntu web-server-01
# Run a one-off remote command
msh ssh --login=ubuntu web-server-01 -- uptime
# Connect using the user@host shorthand
msh ssh ubuntu@web-server-01 Filter Nodes by Label
Use the --filter flag on msh ls to narrow the node
list by label. --filter is repeatable and applies AND logic across
keys. msh ssh itself targets a single hostname — use msh ls --filter=... to find the right node first.
# List nodes matching env=staging
msh ls --filter=env=staging
# Combine multiple filters (all must match)
msh ls --filter=env=production --filter=team=platform
# HOSTNAME ROLE STATUS LABELS VERSION
# web-server-01 node online env=production,os=ubuntu,team=platform 0.1.0
# web-server-02 node online env=production,os=ubuntu,team=platform 0.1.0 Specify Login User
The --login flag specifies which OS user to authenticate as on
the remote node. The login must be listed in your role's logins field (the allow.logins list in the role spec).
# Explicit login flag
msh ssh --login=deploy web-server-01
# user@host shorthand
msh ssh deploy@web-server-01
# If your role uses template variables, your Mezite username may work:
msh ssh --login=alice web-server-01 SCP File Transfers
Use msh scp to transfer files through the Mezite proxy. All transfers
are authenticated with your certificate and logged in the audit trail.
# Upload a file to the remote node
msh scp ./deploy.tar.gz ubuntu@web-server-01:/tmp/
# Download a file from the remote node
msh scp ubuntu@web-server-01:/var/log/app.log ./
# Recursive directory upload
msh scp -r ./config/ ubuntu@web-server-01:/etc/app/
# Note: msh scp does not support remote-to-remote copies — one of src/dst
# must always be local. SSH ProxyCommand Integration
If you prefer to use the native ssh client (for editor integration,
Ansible, or other tooling), configure msh as a ProxyCommand.
This routes your native SSH sessions through the Mezite proxy with full certificate
auth and audit.
# Generated by msh config after msh login.
Host web-server-01.production.mezite
HostName web-server-01
ProxyCommand msh proxy ssh %r@%h:%p
IdentityFile ~/.mezite/profiles/<proxy>/keys/ssh_key
CertificateFile ~/.mezite/profiles/<proxy>/keys/ssh_key-cert.pub
UserKnownHostsFile ~/.mezite/known_hosts
# Then use native ssh:
# ssh ubuntu@web-server-01.production.mezite Prefer the generated config for day-to-day use: it includes the right identity files and per-node host aliases, so OpenSSH validates each node's host certificate against the exact node name.
You can generate this configuration automatically:
# Print SSH config for every node visible from your active profile
msh config
# Or append directly to ~/.ssh/config
msh config --append
# Now use native ssh directly
ssh ubuntu@web-server-01.mezite
This is particularly useful for tools like rsync, sshfs, and Ansible that rely on the native ssh binary.
Port Forwarding
SSH port forwarding is gated by the port_forwarding role
option and the check runs in the proxy, not the agent. Forwarding is
allowed when any of the user's roles has port_forwarding: true. It is denied only when none of the user's roles
allow it, in which case the proxy refuses the channel and emits an
access.denied.port_forwarding audit event with code
T4002W.
msh ssh does not register OpenSSH-style -L,
-R, or -D flags. To use native port forwarding,
configure msh proxy ssh as a ProxyCommand (see the section above)
and run the standard ssh binary with the forwarding flags you
want.
Session Recording and Playback
Agent SSH sessions are recorded by the agent (mezd). The
agent captures terminal I/O at the PTY level — the clean text you see in
your terminal, not encrypted SSH protocol bytes. Recordings are uploaded
to the auth service via gRPC and stored in the configured backend (local filesystem or s3).
The recording mode is set via MEZITE_RECORDING_MODE on the agent.
The default is node, which buffers the recording locally
and uploads it after the session ends. Set
MEZITE_RECORDING_MODE=node-sync to stream chunks to the auth
service in real time instead — the agent will terminate the SSH session if
the upload stream breaks.
# List recent sessions
msh sessions ls
# SESSION ID USER NODE LOGIN STARTED ENDED
# a1b2c3d4-e5f6-7890-abcd-ef1234567890 alice web-server-01 ubuntu 2026-04-11T10:28:35Z 2026-04-11T10:41:09Z
# Play back a session in your terminal
msh play a1b2c3d4-e5f6-7890-abcd-ef1234567890
# Play back at 2x speed
msh play --speed=2 a1b2c3d4-e5f6-7890-abcd-ef1234567890 Administrators can view all sessions; regular users can only view their own. See the Session Recording guide for recording modes, storage backends, and encryption configuration.
Advanced Configuration
Enhanced Session Recording (eBPF)
For compliance requirements, you can enable enhanced session recording
which uses eBPF to capture individual commands executed within the
session. This requires Linux and a privileged container or root access.
It is opt-in via the MEZITE_BPF_ENABLED environment variable.
# Enable eBPF command capture on the agent
MEZITE_BPF_ENABLED=true mezd start Session Moderated Access
Status: Available — Moderated sessions are implemented. Sessions
with require_session_join policies block until the required moderators
join via the web API. Moderator leave terminates the session.
For sensitive environments, require a second user to observe or approve sessions in real time:
kind: role
version: v1
metadata:
name: ssh-sensitive
spec:
options:
require_session_mfa: "totp"
# require_session_join is repeatable; each entry specifies who must be
# present and in what mode for sessions opened with this role.
require_session_join:
- name: require-auditor-observer
# filter is a predicate expression evaluated against the joining user.
filter: 'contains(user.roles, "auditor")'
modes:
- observer
count: 1
on_leave: terminate
allow:
node_labels:
env: production
sensitivity: high
logins:
- root Troubleshooting
Agent fails to join
-
Verify the join token has not expired:
mezctl tokens ls -
Check network connectivity from the agent to the proxy on port
3024. - Review agent logs:
journalctl -u mezd -f
Connection refused or timeout
-
Confirm the node appears in
msh ls. If not, the agent may not be connected. - Check that your role grants access to the node's labels and the login you are using.
- Verify your user certificate is valid:
msh status
Permission denied
-
The login (
root,ubuntu, etc.) must be listed in your role'sloginsfield. -
The node's labels must match your role's
node_labelsselector. -
Inspect your assigned roles with
mezctl users listand examine each role's allow/deny rules withmezctl roles get <name>to check for a deny rule overriding your allow rule.
Next Steps
- Session Recording — Learn how SSH sessions are recorded and played back.
- RBAC Configuration — Fine-tune who can access which nodes.
- Audit Logging — Query and manage SSH audit events.
- SSO Setup — Integrate with your identity provider for certificate issuance.
- Access Requests — Set up just-in-time access for SSH.