Quickstart: Self-Hosted

Prefer the managed path? The Quickstart overview splits managed and self-hosted side by side — the managed path is less operational work over time if you don't want to run the hub yourself.

This guide walks you through a complete Mezite deployment on a single machine — from installing the server to SSH-ing into your first node. It should take about five minutes.

By the end you will have a running Mezite auth/proxy server, an enrolled agent node, an admin user, and a working SSH connection through the Mezite proxy.

Development setup only. Every step below uses --insecure / MEZITE_AUTH_H2C=true / --password-on-the-command-line conveniences that are fine on a single dev box and unsafe on anything else. --insecure disables TLS verification, plaintext h2c presents the gRPC API in the clear, and passing the admin password on the command line leaks it into the process table and shell history. Before exposing this cluster to any other machine, work through the Step 7: Harden for production section: terminate TLS, drop --insecure everywhere, replace MEZITE_ADMIN_PASSWORD with the mezctl bootstrap flow, and close port 22 on the node. See the Systemd, Reverse Proxy, and Architecture guides for the production shape.

Prerequisites

  • Linux or macOS host
  • Podman or Docker (only needed if using PostgreSQL — SQLite requires no external services)
  • Mezite binaries installed (see Installation)

Step 1: Install Mezite

If you have not already installed the Mezite binaries, grab them now. The quickest method on Linux:

Install binaries bash
curl -fsSL https://releases.mezite.com/latest/mezite-linux-amd64.tar.gz \
  | tar -xz -C /usr/local/bin/

# Confirm installation
mezhub version

Step 2: Choose a Database

Mezite stores all cluster state — users, roles, certificate authorities, audit events — in a database. You can use SQLite (zero dependencies, great for getting started) or PostgreSQL (recommended for production).

Option A: SQLite (default, simplest)

No external services needed. SQLite is the default — Mezite creates the database file automatically:

Start mezhub with SQLite (default) bash
MEZITE_CLUSTER_NAME=my-cluster \
MEZITE_ADMIN_PASSWORD='S3cureP@ssw0rd' \
MEZITE_AUTH_H2C=true \
mezhub

That's it — no database to install. The SQLite file is created at /var/lib/mezite/mezhub.db. Skip to Step 3.

Option B: PostgreSQL

Start a local PostgreSQL instance with Podman (or Docker):

Start PostgreSQL 16 bash
podman run -d --name mezite-postgres \
  -e POSTGRES_USER=mezite \
  -e POSTGRES_PASSWORD=mezite \
  -e POSTGRES_DB=mezite \
  -p 5432:5432 \
  postgres:16

# Wait a few seconds for PostgreSQL to become ready
podman logs -f mezite-postgres 2>&1 | grep -m1 "ready to accept connections"

Then start mezhub with PostgreSQL configuration:

Start mezhub with PostgreSQL bash
MEZITE_CLUSTER_NAME=my-cluster \
MEZITE_DB_DRIVER=postgres \
MEZITE_DB_HOST=localhost \
MEZITE_DB_PORT=5432 \
MEZITE_DB_USER=mezite \
MEZITE_DB_PASSWORD=mezite \
MEZITE_DB_NAME=mezite \
MEZITE_DB_SSLMODE=disable \
MEZITE_ADMIN_PASSWORD='S3cureP@ssw0rd' \
MEZITE_AUTH_H2C=true \
mezhub

# You should see output like:
# {"level":"info","msg":"starting mezhub","cluster":"my-cluster"}
# {"level":"info","msg":"database connected"}
# {"level":"info","msg":"migrations applied"}
# {"level":"info","msg":"certificate authorities initialized"}
# {"level":"info","msg":"default admin user created"}
# {"level":"info","msg":"gRPC auth server listening","addr":"0.0.0.0:3025"}
# {"level":"info","msg":"HTTPS proxy listener started","addr":"0.0.0.0:3080"}
# {"level":"info","msg":"SSH proxy listener started","addr":"0.0.0.0:3023"}
# {"level":"info","msg":"tunnel listener started","addr":"0.0.0.0:3024"}

Leave this terminal running (or add & to background the process). Open a new terminal for the remaining steps.

You can also use a config file: mezhub --config=mezite.yaml. See the Configuration Reference for details.

Step 3: Log In with msh

The msh client authenticates against the proxy, receives a short-lived SSH certificate, and stores it locally. The --insecure flag skips TLS verification for local development.

Client login bash
msh login --proxy=localhost:3080 --user=admin --password='S3cureP@ssw0rd' --insecure

# Output:
# Logged in as admin
# Roles:       admin
# Valid until: 2026-03-31T04:00:00Z

Step 4: Add a Node

To enroll a remote machine, install the mezd binary on it and join it to the cluster. First, generate a join token from the server using mezctl.

mezctl needs an auth token. Log in first, then export the token:

Authenticate mezctl and create a join token bash
# Log in and save the session token
export MEZITE_AUTH_TOKEN=$(mezctl --insecure login --username=admin --password='S3cureP@ssw0rd')

# Create a join token for the node agent
mezctl --insecure tokens create --type=static --roles=node --ttl=1h

# Output:
# Token: d4f8a2e1-7b3c-4d9e-a5f6-1234567890ab
# Valid for: 1h
# Use this token to join a node to the cluster.

On the target node, install mezd and start it with the token:

Start the agent on the target node bash
# Install mezd on the target node
curl -fsSL https://releases.mezite.com/latest/mezite-linux-amd64.tar.gz \
  | tar -xz -C /usr/local/bin/ mezd

# Start the agent, pointing it at your proxy's tunnel address.
MEZITE_JOIN_TOKEN=d4f8a2e1-7b3c-4d9e-a5f6-1234567890ab \
MEZITE_AUTH_ADDR=your-server:3025 \
MEZITE_PROXY_ADDR=your-server:3024 \
mezd start

# Output:
# {"level":"info","msg":"joined cluster","cluster":"my-cluster","node":"webserver-01"}
# {"level":"info","msg":"reverse tunnel established","proxy":"your-server:3024"}

Step 5: SSH to the Node

With the agent running and connected, you can now SSH through the Mezite proxy.

SSH through Mezite bash
# List available nodes
msh ls --insecure

# Output:
# Node Name      Address          Labels
# -------------- ---------------- ----------------
# localhost       127.0.0.1        env=local
# webserver-01   192.168.1.50     env=staging

# SSH to a node
msh ssh --login=root webserver-01

# You are now connected through the Mezite proxy.
# This session is being recorded for audit.
webserver-01 $

Step 6: (Optional) Publish an Internal App

Beyond SSH, Mezite can publish an internal web or TCP application to your users without a VPN. This is optional — skip it if you only need SSH. The Application Access guide covers the full flow; here is the shortest path on a self-hosted cluster.

First, pick a parent domain for app hostnames, publish a wildcard DNS record pointing at the proxy, and have a TLS certificate that covers it. Then point mezhub at the domain:

mezite.yaml — enable application access yaml
proxy:
  apps_domain: apps.example.com   # *.apps.example.com -> the proxy (wildcard DNS + TLS)
  apps_oidc_connector: okta       # SSO connector that gates browser app access
  # apps_keypair:                 # optional — only if the main HTTPS cert
  #   cert_file: /etc/mezite/apps-wildcard.crt   # doesn't already cover the
  #   key_file: /etc/mezite/apps-wildcard.key    # *.apps.example.com wildcard

Restart mezhub to pick up the change, then register an app and bind it to the agent you enrolled above:

Register an internal app and open it bash
mezctl --insecure apps register \
  --name=grafana \
  --uri=http://10.0.3.12:3000 \
  --public-addr=grafana.apps.example.com \
  --labels=env=staging \
  --agent-hostname=webserver-01

# Open it in a browser — the proxy gates the visit through your SSO connector:
#   https://grafana.apps.example.com
If apps_domain is left unset, application access stays off and nothing else about the cluster changes.

What Just Happened?

Here is the full certificate flow you just exercised:

  1. mezhub started with both Auth and Proxy services. On first boot it ran database migrations and generated User CA, Host CA, and SPIFFE CA key pairs (ECDSA P-256), storing them encrypted in the database (SQLite or PostgreSQL).
  2. msh login authenticated against the Auth service via the Proxy, received a short-lived SSH certificate signed by the User CA, and saved it under ~/.mezite. The certificate encodes the user's identity, roles, and allowed principals.
  3. mezd used its join token to register with the Auth service, received a host certificate signed by the Host CA, and established a reverse tunnel to the Proxy on port 3024.
  4. msh ssh opened an SSH connection to the Proxy on port 3023. The Proxy verified the user's certificate against the User CA, resolved the target node, and forwarded the connection through the agent's reverse tunnel. The agent verified the user's certificate and started the SSH session. The session was recorded and an audit event was written to the database.
Certificate trust chain text
User CA (ECDSA P-256)                Host CA (ECDSA P-256)
    │                                       │
    ▼                                       ▼
User Cert                              Host Cert
  identity: admin                        host: webserver-01
  roles: [admin]                         cluster: my-cluster
  principals: [root, admin]              valid: 24h
  valid: 12h                                │
    │                                       │
    │  presented to ──▶ Agent           presented to ──▶ msh
    │  (agent trusts User CA)           (msh trusts Host CA)
    ▼                                       ▼
Mutual verification: both sides reject expired, revoked, or unknown certs

Step 7: Harden for Production

The setup above is fine for a single dev box. Before exposing this cluster to anyone else, walk through the items below.

  1. Terminate TLS. Either put mezhub behind a TLS-terminating load balancer / reverse proxy (Nginx, HAProxy, Cloudflare, AWS ALB) and keep MEZITE_AUTH_H2C=true, or provision a real certificate for mezhub itself and drop MEZITE_AUTH_H2C. See the Reverse Proxy guide for the recommended topology.
  2. Set proxy.public_addr. Required for WebAuthn and for the cluster's GetServerInfo enrollment URL. Set it to the public hostname the proxy is reachable on (e.g. mezite.example.com:443).
  3. Drop --insecure. Every msh and mezctl call should validate the proxy's certificate against your CA. Roll your ~/.mezite directory before doing so to make sure no stale insecure session lingers.
  4. Replace MEZITE_ADMIN_PASSWORD. The bootstrap admin password should be a one-time value. Once a real admin account exists (created with mezctl users create), remove MEZITE_ADMIN_PASSWORD from the environment and disable or delete the bootstrap account. Enroll an MFA factor on the new admin with mezctl users add-mfa.
  5. Set MEZITE_CA_KEY_PASSPHRASE. CA signing keys are encrypted at rest only when this is set. Without it, a database dump leaks signing material.
  6. Set MEZITE_AUDIT_HMAC_KEY. Required for tamper-detection on the audit chain. Treat the value as a secret of the same sensitivity as the database password.
  7. Close direct port 22 on each node. With the agent active, the only path into the node should be through the proxy. Block port 22 inbound at the firewall / security group and remove keys from /root/.ssh/authorized_keys / /home/*/.ssh/authorized_keys to prevent fall-through to legacy SSH. The agent itself does not need port 22 open — it owns the session-establishment side of SSH.
  8. Run mezhub as a service. Foreground invocation is fine for the tutorial, but production should run under systemd, Kubernetes, or your container orchestrator of choice with restart policy and log capture configured. See the Systemd / Kubernetes / Podman deployment guides.
  9. Plan database backups. All cluster state lives in the database file. For SQLite, snapshot the file with sqlite3 .backup on a schedule. For PostgreSQL, use your existing PG backup tooling.

Next Steps

  • SSH Access Guide — Deep dive into SSH certificate authentication, session recording, and advanced SSH features.
  • RBAC Guide — Create roles with fine-grained label-based permissions and deny rules.
  • SSO Guide — Configure OIDC, SAML, LDAP, or GitHub OAuth for enterprise authentication.
  • Audit & Recording — Query audit logs and replay SSH sessions.
  • Application Access — Publish internal web and TCP apps through the proxy with SSO, RBAC, and identity injection.
  • Configuration Reference — Tune session TTLs, logging, TLS, and more.
  • Systemd Deployment — Run Mezite as a system service in production.