Access Control
Uplink is private by default — every verified member of the app’s owning account
gets in. For a personal account, that is just you. For an organization account, that
is everyone in the organization. Access is additive: invited email addresses and a
bearer token each add another independent way in, while --open opens the route to
everyone. The edge compiles them into one policy per route and evaluates it before any
traffic reaches your app; unknown hosts collapse to a generic not-found.
This page covers the semantics of each way in. An IP allowlist can further restrict these gated access modes; the tunnel’s base request limit remains a first-class firewall setting. For the flags and YAML fields, see Serve a Port and Formations.
Identity and private routes
Private routes are served through the same public edge URL as everything else, but the edge requires visitor identity before forwarding. Access is admitted for:
- every verified member of the owning account, and
- anyone added by email with
uplink share <app> <email>orshare:inuplink.yaml.
A visitor opens the URL, completes a one-time emailed-code login at the edge, and is
admitted if their identity is on the list — no VPN and no client install. Because the
edge must verify who the visitor is, account-based access (owning-account membership
and share) requires a managed, auth-backed edge and a control plane. Key-mode
self-hosted edges can’t gate on account identity; use a bearer token there instead.
uplink share demo contractor@example.com # invite one person
uplink unshare demo contractor@example.com # revoke
Share with an organization
Users can belong to any number of organizations. To share an app with one of them,
make that organization the app’s owning account. Every organization member can then
open the private app; there is no separate per-app team switch.
Create an organization and invite its members from Accounts in the web dashboard. In the desktop app, choose Organizations → Use account before creating a tunnel. That selection affects new tunnels only; existing tunnels keep their owner. From the CLI, list the account IDs available to your login, then choose one explicitly:
uplink status --output=json
uplink serve 3000 --name dashboard --account acct_acme
In uplink.yaml, omitting account uses your personal account; an organization-owned
service names the organization’s stable slug. share: remains useful for granting
access to people outside that account:
services:
dashboard:
account: acme
port: 3000
share: [contractor@ext.com] # organization members, plus this person
Bearer tokens
A bearer token is a non-account way in — good for webhooks, scripts, and simple shared secrets, and the way to gate a route on a self-hosted key-mode edge:
uplink serve 3000 --auth s3cr3t
services:
api:
account: acme
port: 8080
password: ${API_TOKEN} # a bearer-token way in (hashed locally)
The raw token is hashed locally with SHA-256 before storage or edge registration.
Non-browser clients send Authorization: Bearer <token>; browsers get an edge
token-entry page whose unlock grant (up_bearer) is separate from account session
cookies. A token is additive — it stacks with owning-account membership and share
on the same route.
Restrict access by IP
Choose Restrict to IPs beside Invite emails and Add API token in the Access panel, then add exact IPv4 or IPv6 addresses and CIDR ranges. Requests must come from one of those sources before the owning-account, invite, or token policy is evaluated. The field autosaves on an existing tunnel.
services:
admin:
account: acme
port: 3000
allow_ips: [203.0.113.0/24]
IP restriction is a gated-access option, so it is unavailable while Public access is enabled. Public access temporarily bypasses the saved restriction; turning it off restores the allowlist.
Choosing how to share
| You want… | Use |
|---|---|
| Only you to reach an internal app | Own it with your personal account; add no extra access keys |
| Every member of an organization to reach it | Make the organization the owning account |
| A specific person outside the owning account to reach it | share: [<email>] |
| A webhook or script with a shared secret | password: (bearer token) |
| Access only from trusted networks | allow_ips: [<IP-or-CIDR>] |
| Gated access on a self-hosted key-mode edge | password: (account keys need a managed edge) |
| A public demo or callback URL | public: true |
These combine: an organization-owned route can invite someone outside the organization
and carry a token, while allow_ips restricts all of those ways in. Only
public: true is exclusive — it temporarily bypasses the gated access settings.
For the protocol-level guarantees behind enforcement — holder-of-key binding, fail-closed routing — see the security model.