CLI reference: mt
mt is one native binary for macOS, Linux and Windows (amd64 and arm64) with nothing else to install. It enrolls a workload and keeps its certificate renewed, calls services over mTLS, runs the Mutual Trust enforcer and the integration adapters, and installs itself as a system service. Keys are generated locally and never leave the machine.
Install
The install scripts detect the operating system and architecture, download the release archive, verify the Ed25519 signature on the release's SHA256SUMS with the Mutual Trust release key built into the script, check the archive's SHA-256 against the signed list, and install mt. No runtime, package manager or administrator rights are required.
curl -fsSL https://mutualtrust.io/install.sh | shirm https://mutualtrust.io/install.ps1 | iex| Variable | Default | Meaning |
|---|---|---|
MT_VERSION | latest | Version to install. |
MT_INSTALL_DIR | /usr/local/bin when writable, else ~/.local/bin; on Windows %ProgramFiles%\MutualTrust when elevated, else %LOCALAPPDATA%\Programs\MutualTrust (added to PATH) | Target directory. |
MT_REQUIRE_SIGNATURE=1 | off | install.sh only: refuse to install when the signature cannot be checked. |
MT_OPENSSL | found automatically | install.sh only: path to an OpenSSL 3 binary. |
Both installers verify the release signature before installing. install.sh checks the Ed25519 signature (SHA256SUMS.sig) when OpenSSL 3 is present, and otherwise the ECDSA P-256 signature (SHA256SUMS.p256.der.sig) with any openssl, including the LibreSSL that macOS ships. Set MT_REQUIRE_SIGNATURE=1 to refuse an install that cannot be verified. install.ps1 verifies the P-256 signature using .NET.
Packages and archives
Every release is published at https://mutualtrust.io/downloads/mt/VERSION/; /downloads/mt/latest holds the current version.
| File | Use |
|---|---|
mt_VERSION_OS_ARCH.tar.gz, mt_VERSION_windows_ARCH.zip | The mt binary with the systemd units, for darwin, linux and windows on amd64 and arm64. |
mt_VERSION_linux_ARCH.deb, .rpm | Installs /usr/bin/mt, the mt-renew and mt-enforcer systemd units (not enabled), /etc/default/mt, /etc/mutualtrust/enforcer.yaml and /var/lib/mutualtrust (mode 0700). |
mt-enforcer_VERSION_OS_ARCH.tar.gz, .zip | The standalone enforcer, for hosts that run only the enforcement plane. |
SHA256SUMS, SHA256SUMS.sig | SHA-256 of every file, and its raw Ed25519 signature with the release key. |
curl -fsSLO https://mutualtrust.io/downloads/mt/1.0.0/mt_1.0.0_linux_amd64.deb
sudo apt install ./mt_1.0.0_linux_amd64.debsudo dnf install https://mutualtrust.io/downloads/mt/1.0.0/mt_1.0.0_linux_amd64.rpmA container image (distroless, nonroot, mt renew --watch by default, state in the /var/lib/mutualtrust volume) builds from services/mt/Dockerfile: docker build -f services/mt/Dockerfile -t mt services.
Verify a download
The release public key is published at mutualtrust.io/downloads/mt-release.pub and is built into mt self-update and both install scripts. With OpenSSL 3:
B=https://mutualtrust.io/downloads/mt/1.0.0
curl -fsSLO $B/SHA256SUMS -O $B/SHA256SUMS.sig -O $B/mt_1.0.0_linux_amd64.tar.gz
curl -fsSL https://mutualtrust.io/downloads/mt-release.pub -o mt-release.pub
openssl pkeyutl -verify -pubin -inkey mt-release.pub -rawin -in SHA256SUMS -sigfile SHA256SUMS.sig
sha256sum --ignore-missing -c SHA256SUMS # macOS: shasum -a 256 --ignore-missing -c SHA256SUMSGlobal options
| Flag | Environment | Default | Meaning |
|---|---|---|---|
--api URL | MT_API | https://api.mutualtrust.io, or the control plane the identity was enrolled with | Control-plane base URL. |
--home DIR | MT_HOME | see below | State directory: the workload identity in DIR/workload, gateway pairings in DIR/gateway. |
--json | off | One JSON document on stdout; progress goes to stderr. | |
--no-color | NO_COLOR | colors on a terminal | Plain output. |
| Platform | Default | As root or a service |
|---|---|---|
| Linux | $XDG_CONFIG_HOME/mutualtrust (~/.config/mutualtrust) | /var/lib/mutualtrust |
| macOS | ~/Library/Application Support/MutualTrust | /Library/Application Support/MutualTrust |
| Windows | %ProgramData%\MutualTrust | %ProgramData%\MutualTrust |
Flags may appear before or after positional arguments. mt help COMMAND (or mt COMMAND --help) prints a command's flags.
Upgrading from the JavaScript CLI. When the default home has no identity and ~/.mt does, mt copies ~/.mt/workload and the gateway pairings into the default home on first use and leaves a MIGRATED.txt note in ~/.mt; the old files are left in place for anything that still reads them. With --home ~/.mt (or MT_HOME), mt uses that directory as it is: both CLIs read and write the same files.
mt gateway
Starts the sample Orders API and runs the enforcer, which is built into mt, in proxy mode in front of it. It pairs the gateway (first run only) and keeps running. mt quickstart is an alias.
mt gateway [--port 8443] [--origin-port 9001 | --origin URL] [--admin-port N]
mt gateway pair --print-credentials [--out FILE] [--connector cloudflare-worker]| Flag | Default | Meaning |
|---|---|---|
--port N | 8443 | HTTPS (mTLS) listener on 127.0.0.1. |
--origin-port N | 9001 | Port of the sample Orders API on 127.0.0.1. It rejects requests that did not come through the gateway. |
--origin URL | sample API | Put the gateway in front of your own origin instead of starting the sample Orders API. |
--admin-port N | port + 1 | Enforcer admin listener on 127.0.0.1: /metrics, /healthz, /readyz. |
--log-format F | text | Enforcer log format: text or json. |
--enforcer PATH | built in | Run this standalone mt-enforcer binary instead of the built-in enforcer. |
- On first run the enforcer generates a P-256 gateway key, prints a pairing code and approval URL, and waits until the code is approved in the portal. The approved credential and pinned policy-verification key are saved to
DIR/gateway/gateway-state.json(mode 0600); later runs reuse them. - A self-signed localhost server certificate is created at
DIR/gateway/server.pem;mt calltrusts it automatically. - The gateway refreshes policy every 15 to 17 seconds, sends signed acknowledgments and receipts, and logs every decision with its request ID, verdict, reason, principal and policy revision.
- Ctrl-C flushes pending receipts and stops. To pair again, delete
gateway-state.json.
mt gateway pair --print-credentials pairs an enforcer that runs elsewhere (the Cloudflare Worker) and prints its credentials as JSON for wrangler secret bulk; the pairing code goes to stderr. To enforce in front of a real API, see the standalone mTLS proxy guide or the guide for your gateway under Gateways.
mt enroll
mt enroll --token mtenr_… [--api URL] [--key-store file|keychain]
mt enroll --workload wl_… --grant gr_… --oidc-token-file FILE [--api URL]| Flag | Meaning |
|---|---|
--token mtenr_… | Sandbox enrollment token from the portal (single use, 30 minutes). |
--oidc-token-file FILE | File containing an OIDC workload JWT (for example a projected Kubernetes service-account token). Requires --workload and --grant. |
--workload wl_…, --grant gr_… | Workload and grant IDs (OIDC enrollment). |
--key-store STORE | Where the private key is kept: file (default) or keychain (macOS). Also MT_KEY_STORE. See Key storage. |
--timeout D | How long to wait when signing is pending (default 10m). |
Generates a P-256 key, sends a CSR and proof of possession, polls while signing is pending, and writes the identity. See Enrollment.
mt renew
mt renew [--force] [--watch] [--on-renew COMMAND] [--signal SIG --pid PID | --pid-file FILE]| Flag | Meaning |
|---|---|
| (none) | Renew once if renew_after has passed; otherwise print when it will be due (exit 0). |
--force | Renew now regardless of the renewal window. |
--watch | Run continuously: sleep until the window opens (checking at least hourly), renew, reload, confirm, repeat. Failures back off from 5 s to 5 min with jitter. |
--on-renew COMMAND | Run after the new files are written, for example --on-renew "systemctl reload nginx". Also MT_ON_RENEW. Executed directly, without a shell; quotes group words. |
--signal SIG | Send HUP, USR1, USR2, TERM or INT to --pid PID or the PID in --pid-file FILE (Linux and macOS). |
--hook-timeout D | Maximum run time of the hook (default 60s). |
--key-store STORE | Keep the new key in this store (moves the identity between stores). |
The new key, certificate, chain and identity.json are committed together: each file is staged and synced, a journal marks the commit, and the files are renamed into place, so a crash never leaves a key next to the wrong certificate. The hook receives MT_CERT_FILE, MT_CHAIN_FILE, MT_KEY_FILE (file store), MT_ROOT_FILE, MT_CERTIFICATE_ID and MT_SERIAL. Only when the hook succeeds does mt confirm the reload, signed by the new key; if the hook fails, the confirmation is held back and the next run (or the next --watch cycle) retries the hook and the confirmation. See Renewal.
renewed 5c1e9a7b3d2f… → 3a7d1c9e5b2f… expires 2026-11-16T07:27:40Z; renew after 2026-11-15T23:31:02Zmt call
mt call URL [--method GET] [--data JSON] [--header 'Name: value'] [--server-ca FILE] [--no-cert]| Flag | Default | Meaning |
|---|---|---|
--method M | GET | HTTP method. |
--data JSON | None | Request body (sent with Content-Type: application/json). |
--header H | None | Extra request header; repeatable. |
--server-ca FILE | for localhost: DIR/gateway/server.pem if present; otherwise the system roots | CA to trust for the gateway’s server certificate. |
--no-cert | off | Do not present the client certificate, useful to see no_client_certificate. |
--timeout D | 30s | Request timeout. |
Presents DIR/workload/chain.pem with the key from the identity's key store, and deliberately adds a spoofed x-mt-principal header so you can confirm the gateway strips it. Prints the verdict, request ID, policy revision and body.
mt status
urn:mutualtrust:workload:sandbox:org_01jcqa7m3a2b7c9d4e6f8g1h3j:wl_01jcqb3p1y2z4a6b8c0d2e4f6g
certificate crt_01jcqb5r0d2e4f6g8h0j2k4m6n serial 5c1e9a7b3d2f48a6b0c4e8d1f3a5b7c9
expires 2026-11-15T15:30:30Z (1310 min)
renew after 2026-11-15T07:26:41Z
renewals 0
key store file
files /home/app/.config/mutualtrust/workloadExits 5 when no identity is enrolled, and 1 when the certificate has expired.
mt service
Installs mt renew --watch (default) or mt gateway as a system service that starts at boot and restarts on failure, using this binary, the current --home and, when given, --api. Arguments after -- are appended to the service command.
mt service install [renew|gateway] [--user] [--name NAME] [--dry-run] [-- FLAGS]
mt service uninstall [renew|gateway] [--user] [--name NAME] [--dry-run]sudo mt service install renew -- --on-renew "systemctl reload nginx"
mt service install gateway --user -- --origin http://127.0.0.1:3000
mt service install renew --dry-run # print the unit and the commands; change nothing| Platform | System-wide (root or elevated) | Per user (--user) |
|---|---|---|
| Linux (systemd) | /etc/systemd/system/mt-renew.service, enabled and started; logs: journalctl -u mt-renew | ~/.config/systemd/user/mt-renew.service (systemctl --user) |
| macOS (launchd) | LaunchDaemon /Library/LaunchDaemons/io.mutualtrust.mt-renew.plist; logs in /Library/Logs/MutualTrust | LaunchAgent in ~/Library/LaunchAgents (the default when not root); logs in ~/Library/Logs/MutualTrust |
| Windows | Service mt-renew (automatic, delayed start; restarts after 10 s, 30 s, 1 min); logs in %ProgramData%\MutualTrust\logs | Not available |
The .deb and .rpm packages ship equivalent mt-renew and mt-enforcer units that read MT_API and MT_ON_RENEW from /etc/default/mt (or /etc/sysconfig/mt).
mt enforcer
The enforcer is built into mt: mt enforcer MODE takes the same commands and flags as the standalone mt-enforcer binary (proxy, envoy-grpc, forward-auth, lambda, run --config FILE, pair, credentials, bundle, healthcheck, version), described in the gateway guides.
mt enforcer proxy --api URL --state DIR --listen 0.0.0.0:8443 --origin http://127.0.0.1:9001 --server-cert C --server-key K
mt enforcer install [--dir DIR] [--from URL] # download the standalone mt-enforcer (SHA-256 verified)
mt enforcer whichThe standalone mt-enforcer remains available for containers, Lambda and gateway sidecars: mt enforcer install, the mt-enforcer_* release archives, or services/enforcer/Dockerfile.
mt adapt
The integration adapter runtime is built in too: mt adapt COMMAND is mt-adapt COMMAND (list, capabilities, assess, inspect, desired, plan, render, apply, verify, rollback, observe, report, approval). See Integration adapters.
mt discover
Read-only migration discovery: does a connection depend on publicly issued client certificates? Sectigo published 10 February 2027 as the final cutoff for clientAuth in newly issued publicly trusted Sectigo SSL/TLS certificates, and inventory and renewal timing decide the effect on each deployment. Every finding is labelled verified, inferred or unknown and names its source. The workflow, delegation and the meaning of each label are in Migration discovery.
mt discover endpoint HOST[:PORT] [--sni NAME] [--http-probe] [--allow-private] [--timeout 10s] [--interval 250ms] [--out FILE]
mt discover certs PATH... [--os-store] [--target HOST[:PORT]] [--cutoff 2027-02-10] [--renewal-lead 30] [--keytool FILE] [--out FILE]
mt discover config FILE|DIR [--platform auto|nginx|envoy|haproxy|traefik|kong|f5|apache|k8s|java|aws|azure] [--redact-hosts] [--out FILE]
mt discover report [--from FILE]... [--endpoint HOST[:PORT]]... [--certs PATH]... [--config PATH]... [--target HOST[:PORT]]
[--redact-hosts] [--submit --workspace ID [--token-file FILE]] [--out FILE]mt discover endpoint api.example.com --http-probe --out endpoint.json
MT_DISCOVER_PASSWORD=... mt discover certs client.p12 keystore.jks --target api.example.com --out certs.json
mt discover config /etc/nginx --out config.json
mt discover report --from endpoint.json --from certs.json --from config.json
MT_TASK_TOKEN=... mt discover report --from endpoint.json --from config.json --submit --workspace cws_...| Subcommand | What it does |
|---|---|
endpoint | TLS handshakes without a client certificate: the server chain, whether the server sends a CertificateRequest and which CA names it accepts, whether it refuses a caller without a certificate, and the supported TLS versions. --http-probe adds one GET / without cookies or credentials to infer the TLS terminator from headers such as Server, Via, CF-RAY and x-amzn-*. At most 8 connections, --interval apart. Private, loopback and link-local addresses are refused unless --allow-private. |
certs | Client certificates in PEM, DER, PKCS#12 and JKS files (JCEKS through the JDK keytool), and with --os-store the macOS Keychain identities or the Windows CurrentUser and LocalMachine MY stores: subject, issuer, EKUs, serial, expiry, key type and size, and whether the issuer chains to a public root. A public certificate with clientAuth whose renewal falls after --cutoff is flagged public_clientauth_renewal_at_risk. Passwords come from $MT_DISCOVER_PASSWORD or a hidden prompt. Private keys are never printed or kept. |
config | The adapter inspectors of mt adapt inspect, run offline on configuration files (no gateway binary runs): client-certificate mode, CA files, verification depth, authorization mapping, termination point and the gateway's documented limitations. The platform is detected from the content. |
report | One MigrationEvidenceV1 document per target from saved results (--from) or collectors run inline, with a plain-language summary (what was found, what it means, what is unknown) or --json. --submit posts the documents that describe the workspace endpoint to POST /v1/workspaces/{id}/assessments with Authorization: Bearer from $MT_TASK_TOKEN (a delegated task token), $MT_API_KEY or --token-file. |
Captured text (headers, certificate fields, CA names, configuration comments) is untrusted: control and markup characters are stripped, and text that reads like instructions is withheld and reported in an untrusted_instructions finding without being printed. Secrets are always redacted; --redact-hosts also masks internal hostnames, private addresses, upstreams and file paths, and is on by default with --submit.
mt version, mt self-update
mt version [--json]
mt self-update [--check] [--version V] [--force]mt self-update downloads SHA256SUMS and SHA256SUMS.sig for the release, verifies the Ed25519 signature with the release key built into the running binary, checks the archive's SHA-256 against the signed list, and replaces the executable atomically. It refuses to touch an install owned by a package manager (/usr/bin/mt from the .deb or .rpm, Homebrew, Scoop or winget) and names the command to use instead; --force overrides.
Key storage
| Store | Where the private key lives | Notes |
|---|---|---|
file (default) | DIR/workload/key.pem, PKCS#8 PEM | Mode 0600 in a 0700 directory; on Windows a protected ACL for SYSTEM, Administrators and the owner. Any TLS server or client can read it (NGINX, Envoy, curl). |
keychain (macOS) | A generic-password item in the macOS Keychain (service io.mutualtrust.mt) | No key file is written. Set MT_KEYCHAIN to a keychain file, for example /Library/Keychains/System.keychain for a LaunchDaemon. mt call, mt renew and the reload confirmation read the key from the keychain. |
The store is recorded in identity.json, so mt renew and mt call find the key without flags. mt renew --key-store STORE moves an identity to another store with its next key.
Files
| Path (under the home directory) | Contents |
|---|---|
workload/key.pem | Workload private key (PKCS#8, mode 0600; file store only). Replaced on each renewal. |
workload/cert.pem | Leaf certificate. |
workload/chain.pem | Leaf followed by the issuing CA. Present this as the client chain. |
workload/root.pem | The space root. |
workload/identity.json | Certificate ID, grant, principal, serial, expiry, renew_after, root fingerprint, renewal history, key store and pending reload confirmation. |
gateway/gateway-state.json | Gateway ID, scoped credential, pinned policy key, gateway key pair and applied high-water mark (mode 0600). |
gateway/server.pem | Self-signed server certificate for localhost (and its key, server-key.pem). |
The layout is the one the JavaScript CLI uses, so either CLI reads the other's files.
JSON output
With --json, enroll, renew, call, status, discover, service --dry-run, enforcer which, version and self-update print one JSON document on stdout. Errors print {"error": {"message", "exit_code", "status", "code", "request_id"}}. mt renew --watch --json prints one document per renewal.
mt status --json | jq -r .identity.renew_afterExit codes
| Code | Meaning |
|---|---|
| 0 | Success, including mt renew when renewal is not due yet; for mt call, an HTTP status below 400. |
| 1 | The operation failed: control-plane error (the message includes the request ID), I/O, signature or checksum mismatch, reload hook failure. |
| 2 | Invalid command line. |
| 3 | mt call: HTTP status 400 or above (for example a 403 denial). |
| 4 | mt call and mt discover endpoint: connection or TLS handshake failed. |
| 5 | No identity is enrolled in the home directory. |
| 6 | mt discover endpoint: the target resolves to a private, loopback or link-local address and --allow-private was not given. |
mt enforcer MODE and mt adapt return the exit codes of mt-enforcer and mt-adapt.
JavaScript CLI (optional)
A single-file JavaScript build of the CLI, mt.mjs (SHA-256), remains available for environments that already standardize on Node.js 20 or later. It covers gateway, enroll, renew, call, status and enforcer install with the same wire formats and files, and keeps its state in ~/.mt. It has no service installer, reload hooks, key stores, built-in enforcer or self-update: use the native mt unless you need it.
node mt.mjs enroll --token mtenr_… --api https://api.mutualtrust.io