Skip to content

Global

One binary, three roles. Everything on this page — and in this section — applies to every tw installation, no matter which role it plays.

One Binary, Three Roles

Every machine runs the same tw binary. What it does is decided by the mode stored in its active context:

Role Who runs it Top-level commands
Relay The admin who owns the relay VM: provisions it, holds the CA, admits servers tw relay create / destroy / test / ssh / status / invite / get-servers / un-enroll-server
Server The operator of the network being exposed: joins a relay, manages users and apps, runs the tunnel endpoint tw server start / test / status / user … / app …
Client The person connecting in: joins via invite or imports a bundle, brings up local port forwards tw client connect / listen / test / status

The mode is set by the first role action a machine performs — tw relay create (relay), tw join <relay-host> <code> against a tw relay invite code (server or client — the issuer's invite decides which), or importing and activating a context bundle (tw config import --activate) — and every context carries exactly one role.

One role per context

Once a context has a role, it keeps it — commands of the other roles refuse to run in it:

Error: this command requires relay mode, but tw is configured in server mode

To act in a different role on the same machine, use a separate context.

Signed Mode (Tamper Evidence)

The mode field in config.yaml is signed (ed25519) against the profile's identity. If the file is hand-edited to flip a client into a server — or the signature no longer matches the identity — tw refuses to run role commands until the profile is restored (re-enroll for servers, re-import for clients, re-create for relays). Relay profiles hold their own signing key and self-heal an unsigned mode.

Tamper evidence, not the security boundary

The signature only makes local tampering evident. The real access control lives on the other end of the wire: the relay routes every tunnel via mTLS (Caddy verifies a CA-issued client certificate and matches it to a tenant before proxying), and the server's SSH authorized_keys gates every user with per-port permitopen restrictions. A tampered local mode gains nothing — the relay and server still reject unknown keys and certificates.

Shared Across All Roles

These commands and facilities work identically in every role:

Feature Command Guide
Contexts — store, switch, export, and import complete identities tw config … Contexts
Unified status — context, mode, relay, live component health tw status Status, Service & Completion
System service — run on boot via systemd / Windows SCM / launchd tw service … Status, Service & Completion
Outbound proxy — route all traffic through SOCKS5/HTTP tw proxy … Proxy
Web dashboard — browser UI on :8080, pages adapt to the role tw dashboard Dashboard
Shell completion — zsh, with dynamic candidates tw completion Status, Service & Completion

Global Flags

Every command accepts these persistent flags:

--log-level string    log level (debug, info, warn, error) — persisted to config
--log-format string   log format (text, json) — persisted to config
--config-dir string   config/state directory to use instead of the system default;
                      flag form of TW_CONFIG_DIR (no permissions needed)

Config Directory

All state — config.yaml, keys, certificates, users, contexts — lives in one directory:

Platform Path
Linux / macOS /etc/tw/config/
Windows C:\ProgramData\tw\config\

Override it with the TW_CONFIG_DIR environment variable or the --config-dir flag — handy for running without root or keeping several independent installations side by side.