CLI Reference
Run any command with no arguments to see its usage line, or uplink help <command>
(subcommands too: uplink help edge add) for its full help. The global
--output human|json|jsonl option (-o) controls rendering consistently across
the command tree. Human output is the default, JSON is one complete document,
and JSON Lines is intended for streams such as followed logs. Progress and prompts
stay on stderr, so machine-readable stdout is safe to pipe. Malformed invocations
(unknown flags, missing values) exit with code 2; runtime failures exit 1.
uplink status --output=json
uplink logs api --follow --output=jsonl
Session
uplink login # choose a browser link or emailed code
uplink login --email me@example.com [--code ABC12345]
uplink login --token <jwt> [--refresh <refresh-token>] # headless identity token (CI)
uplink login --device-token <token> # host-bound, serve-only session
uplink login --auth <url> --control <url> # point at a self-hosted stack
uplink logout
With no credential flags, login asks whether to use a one-time browser link or an
emailed code. For browser login it opens the system browser when possible and always
prints a clickable, copyable fallback URL. login then writes the local profile — account, namespace, device slug, token (and refresh
token when present), and the managed edge registry. The secret-bearing flags each take a
-file variant that reads from a path (or - for stdin, keeping it off ps):
--token-file, --refresh-file, and --device-token-file; --device-token also
reads UPLINK_DEVICE_TOKEN. login --output=json prints the resulting identity in
the same shape as status --output=json — useful in CI after login --token. logout clears the
session and any unpublished local apps but keeps the device identity key.
Serving
uplink serve <port> [--name <name>] [--account <id>] [--public] [--auth <token> | --auth-file <path>] [--share <email>]... [--edges <id[,id...]>] [-d|--detach]
uplink init
uplink up [-f <path>]
uplink reload [-f <path>]
uplink restart
uplink down [-f <path>]
uplink stop <name>
uplink stop --all
uplink managed list
uplink managed attach <url> <port>
uplink managed detach <url>
serve is the quick foreground path for one app; -d/--detach keeps it serving in
the background instead. Machine output requires detached mode and prints the served
app in the same shape as up --output=json for scripting.
init writes a commented starter uplink.yaml in the current directory and refuses
to overwrite an existing one. Access is additive — gated to
verified members of the owning account by default; --account chooses that personal
or organization account, --auth adds a token (--auth-file <path|-> reads it from a
file/stdin), --share invites someone by email, and --public (--open is an alias)
opens it to everyone (exclusive with the rest). uplink status --output=json lists the
accounts available to the current login. up serves the
formation in uplink.yaml in the background and returns;
reload re-reads it, down clears and stops it — with no arguments it stops
everything (including detached UI tunnels), while down -f <path> stops only the
services declared by that config file, leaving other tunnels running. stop removes
one or all served apps. restart stops the background agent and starts it again from
the on-disk binary, re-serving the saved formation — run it after uplink upgrade,
since an in-place upgrade leaves the old agent running until it is restarted.
On managed edges, formations can also assign verified account
custom domains with custom_domain:. Standalone aliases
are configured in an edge-side policy instead.
managed controls this device’s participation in a console-owned
load-balanced endpoint. list shows the
tunnels in the selected account; attach joins one local port to a tunnel’s backend
pool; detach drains and removes this device’s attachment. The public URL and ingress
policy are changed in the console, not by these commands.
Status & sharing
uplink status
uplink share <app> <email>
uplink unshare <app> <email>
uplink logs <app> [--follow]
status shows identity, available accounts, and served apps. share/unshare add or
remove an invited email on a private app — re-serve the app for the change to take
effect. Organization-wide access comes from making
the organization the app’s owning account. logs prints the app’s local event log
(written on this device by the serving side, not fetched from the edge). JSON output
is one array; --output=jsonl emits one object per entry, and --follow keeps
streaming human or JSONL entries until Ctrl-C.
Traffic capture
There is no capture command in the CLI. Request capture is enabled per service:
declaratively with the inspect: off | metadata | full field in uplink.yaml, or
interactively per tunnel in the desktop app / uplink ui inspector. A plain
uplink serve never enables capture. Captures land in the device-local inspector
store the desktop app and web UI browse. See Observability.
Edges & tunnels
uplink edge add <domain> --api-key <uek-key> [--identity-provider <origin>] [--challenge-provider <origin>]
uplink edge add <domain> --connector-auth identity --identity-provider <origin>
uplink edge list # alias: uplink edges
uplink edge refresh <domain> # re-discover saved provider capabilities
uplink edge remove <domain> # alias: uplink edge rm
uplink tunnels
uplink edges
edge manages local self-hosted edges and lists managed edges adopted at login.
In API-key mode, --api-key-file <path|-> can replace --api-key. edge add also
accepts a host:port positional; the host becomes the edge’s domain and the whole
value its dial address. Otherwise the edge is dialed at <domain>:4100 unless
--address overrides the dial authority.
edge refresh updates the saved visitor-identity capability after a provider
changes its discovery document. The edge runtime discovers the provider independently
at startup.
edges (an alias for edge list) works without a control plane — it shows your
self-hosted edges and, when you’re signed in, the managed edges too. Only tunnels
needs a configured control plane: it queries it for live tunnels across your devices.
Devices
uplink device list # alias: uplink devices
uplink device nickname <name> # rename this device (alias: device rename)
uplink device nickname <name> --id <device-id>
A device’s nickname is a friendly label the dashboard and analytics views show
for its device_id — it does not change any public URL (those carry the device
slug). nickname defaults to renaming the current device; pass --id to rename
another of the account’s devices. Both query the control plane.
UI
uplink ui [--port <n>] [--tunnel [--tunnel-name <name>] [--auth <token> | --auth-file <path|->] [-d|--detach]]
uplink api [--socket <path>]
ui serves the embedded desktop UI over HTTP. It always binds 127.0.0.1
(default port 4800); remote access goes through --tunnel, which publishes the UI
as an edge-gated Uplink tunnel — see Desktop App & Web UI.
api serves the same command contract as a local HTTP control API over an
owner-only unix socket (default ~/.uplink/api.sock), for scripts and products
that manage tunnels programmatically — see Local API.
Agent
uplink agent status # alias: uplink agent doctor
Diagnoses the local uplink-agent serving daemon without starting it.
For release tooling and support diagnostics, uplink --build-info and
uplink-agent --build-info emit the same JSON cohort identity: release version,
source build identity, distribution owner, and agent protocol version.
For a broader read-only health report and shell integration:
uplink doctor
uplink doctor --output=json
uplink completions bash
doctor checks local state, installed binaries, and the resident agent in one
report. It always emits the complete report and exits 1 if any check fails.
completions prints a generated script for Bash, Zsh, Fish, Elvish, or
PowerShell; source or install that output using the conventions of your shell.
Serve from boot (uplink service)
uplink service install [--user <name>] [--state-dir <path>] [--dry-run] [--force]
uplink service uninstall [--dry-run]
uplink service status
service runs the connector (uplink-agent) under the system service manager —
systemd on Linux, launchd on macOS (a system-domain LaunchDaemon) — so a
headless box serves its tunnels from boot with nobody logged in. Without it, the
agent dies with the session and never comes back after a reboot. install needs
root (sudo) and an existing session for the target user (--user defaults to the
invoking user; --state-dir to that user’s ~/.uplink); on a headless box log in
first with uplink login --device-token-file <path>. --dry-run prints the service
definition and commands without touching the system; --force overwrites an existing
definition. Declare what it serves with uplink up as usual.
One supervisor per machine. On a machine running the
desktop app, the app already supervises the shared agent, and
install refuses — the two would fight over the single per-user agent. The service
is for headless, CLI-only hosts; on a workstation use the app’s start-at-login
setting instead (--force overrides, if you are sure).
Upgrade
uplink upgrade [--dev] [--check] [--yes] [--install-dir <path>] [--manifest-url <url>]
upgrade checks the release manifest for a newer build and replaces the binary in
place on Linux (x86_64 or arm64) and Apple Silicon macOS. --check only reports whether an
update is available (no install); --yes (-y)
skips the confirmation prompt; --install-dir targets a binary location other than the
running one; --dev upgrades from the dev release channel instead of stable;
--manifest-url overrides the release manifest URL. After an upgrade, uplink restart
cycles a still-running agent onto the new binary (upgrade attempts this itself).
On a machine with the system service installed,
an upgrade of a CLI living outside /usr/local/bin leaves the service on the old
build — re-run sudo uplink service install --force to move it over (upgrade
prints a reminder when this applies).
The CLI installed from Uplink.app is desktop-managed: update it through Uplink →
Settings → Updates so modifying the CLI cannot invalidate the signed app bundle.
Operator commands (uplink-edge)
Run on the edge host, not the connector:
uplink-edge key new [--label <text>] [--file <path>] # mint a connector API key
uplink-edge key list --file <path>
uplink-edge key revoke --file <path> --id <key-id>
uplink-edge key rotate --file <path> [--label <text>] [--overlap-seconds <n>]
uplink-edge container setup # initializer used by the self-hosted Compose bundle
uplink-edge doctor [--domain <host>] [--public-ip <ip> | --no-public-ip-detect] [--identity-provider <url>]
uplink-edge doctor --env-reference # exhaustive edge environment reference
uplink-edge local --domain <name> --tunnel-cert <path> --tunnel-key <path> [--api-key <key>] [--print-env]
uplink-edge --public-listen <addr> --tunnel-listen <addr> --account <id> # plaintext origin behind a TLS proxy
uplink-edge completions zsh
key new prints a raw uek_… key once and, with --file, writes a structured
JSON key file holding only the key’s SHA-256 hash plus metadata — the edge reads that
file via UPLINK_EDGE_API_KEYS_FILE and re-reads it on every connector registration,
so revoke and rotate take effect without a restart (rotate --overlap-seconds
keeps the old keys valid during a cutover). container setup is the initializer for a
self-hosted edge with local API-key or identity-provider connector admission and an
optional challenge provider. It collects the facts, waits for DNS, obtains the TLS
certificate, and writes restricted Docker volume state; it does not require a
management plane. Operators normally invoke it as docker compose run --rm setup
and use the operator Compose service for key and doctor commands. The bare
uplink-edge public listener is plaintext and must remain on loopback or a private
network behind a correctly authenticated TLS proxy. The serving forms require
UPLINK_EDGE_PUBLIC_DOMAIN and the tunnel identity
(UPLINK_TUNNEL_CERT/UPLINK_TUNNEL_KEY, or --tunnel-cert/--tunnel-key paths
for local). Identity-backed connector admission or visitor SSO uses
UPLINK_IDENTITY_PROVIDER_URL. Bot protection uses
UPLINK_CHALLENGE_PROVIDER_URL plus UPLINK_CHALLENGE_PROVIDER_TOKEN. See
Self-Hosting.
uplink-edge doctor always emits its complete report and exits 1 when a check
fails, so its JSON output can be used directly as a deployment health gate.
Common environment variables
| Variable | Used by | Purpose |
|---|---|---|
UPLINK_STATE_DIR | all | CLI state directory (default ~/.uplink) |
UPLINK_DEVICE_TOKEN | login | host-bound, serve-only account credential |
UPLINK_AUTH_URL | login | login / JWKS issuer base URL |
UPLINK_CONTROL_URL | login / uplink-edge | control-plane base URL saved into a connector profile or used for managed-edge reporting; leave unset on a standalone edge |
UPLINK_EDGE | serve / up | default edge domain |
UPLINK_EDGE_ADDRESS | serve / up | dial address for an env-configured self-hosted edge |
UPLINK_EDGE_API_KEY | serve / up | raw self-hosted edge API key |
UPLINK_TUNNEL_TRUST_CERT | serve / up | DER certificate trusted for a self-hosted tunnel endpoint |
UPLINK_TUNNEL_SERVER_NAME | serve / up | expected tunnel certificate server name |
UPLINK_EDGE_PUBLIC_DOMAIN | uplink-edge | the public domain the edge owns (required to serve) |
UPLINK_TUNNEL_CERT / UPLINK_TUNNEL_KEY | uplink-edge | the edge’s tunnel identity cert/key (required to serve) |
UPLINK_IDENTITY_PROVIDER_URL | uplink-edge | discovery authority for connector identity and/or visitor SSO |
UPLINK_EDGE_API_KEYS_FILE | uplink-edge | path to the JSON key file from uplink-edge key new --file (enables key mode; reloaded on every registration) |
UPLINK_EDGE_API_KEYS | uplink-edge | inline JSON key file — tests and emergency one-off runs only |
UPLINK_EDGE_ALIAS_POLICY_FILE | uplink-edge | standalone YAML map from custom hostnames to canonical generated hosts |
UPLINK_EDGE_TRUSTED_PROXY_HOPS | uplink-edge | number of authenticated reverse-proxy hops; setup uses 1 for Caddy |
UPLINK_EDGE_TRUSTED_PROXY_TOKEN | uplink-edge | secret a fronting proxy stamps so forwarded client IPs can be trusted |
UPLINK_EDGE_OPERATOR_TOKEN | uplink-edge | token for detailed operator diagnostics; unset disables that surface |
OTEL_EXPORTER_OTLP_ENDPOINT | uplink-edge | operator-owned OTLP collector endpoint; unset disables export |
This table covers the variables most often set by connector and standalone-edge
operators. uplink-edge doctor --env-reference prints the exhaustive edge list,
including defaults and managed-fleet variables, directly from the edge’s canonical
environment registry.