Tailscale — API Surface#

API types#

Tailscale exposes four distinct API layers:

  1. Local IPC API — Unix socket HTTP REST API (/localapi/v0/) used by the tailscale CLI and GUI frontends to talk to the running tailscaled daemon.
  2. CLI — The tailscale binary, using the ffcli framework (peterbourgon/ff).
  3. Coordination API client — Go library (client/tailscale) for the remote Tailscale cloud control plane REST API at api.tailscale.com/api/v2/.
  4. Library API (tsnet-style) — The client/local package wraps the Local IPC API as a typed Go library; safeweb and ipn/serve expose per-node HTTP serving capability for user applications.

There are no gRPC services and no .proto files in the repository. All IPC is plain HTTP over Unix sockets; all cloud coordination uses custom HTTPS long-polling, not gRPC.


REST/HTTP API — Local API (/localapi/v0/)#

Router#

Custom, statically registered map (var handler = map[string]LocalAPIHandler). Routes are exact-match strings keyed by the path suffix after /localapi/v0/. Some entries are prefix-match (trailing slash). All handlers are bound to (*Handler).serveXxx methods.

Route registration#

Registration happens in two layers:

  • Static map in ipn/localapi/localapi.go (compiled-in always): core routes like status, prefs, ping, whois, profiles/, derpmap.
  • init()-time Register() calls gated on buildfeatures.HasXxx booleans: optional features (SSH dial, TKA, Drive, app connectors, metrics, update, etc.) only register their routes if their build feature is compiled in. This means the route table is lean in embedded or restricted builds.

Middleware / auth chain#

ipnserver.Server.serveHTTP → authentication (Unix socket peer-credentials check, ipnauth.Actor) → per-connection localapi.Handler. No classical middleware stack; authentication happens once per connection at the server level. CSRF protection is absent for the Unix socket API (physical access to the socket is the boundary); CSRF is added only for the web client (gorilla/csrf).

Authentication#

Authentication is done by inspecting Unix socket peer credentials (UID/GID) on Linux/macOS. On Windows, named pipe identity is used. Users identified as the operator user (configured via prefs.OperatorUser) or root get write access; others get read-only access. The ipnauth.Actor interface encapsulates the caller identity and drives permission checks.

Key endpoints (all under /localapi/v0/)#

EndpointMethodsPurpose
statusGETNode status — peers, IPs, relay info, login state
prefsGET, PATCHRead/update preferences (routes, exit nodes, SSH, etc.)
check-prefsGETValidate preference change without applying
profiles/GET, POST, DELETEMulti-profile management (switch tailnets)
pingPOSTPing a peer by IP; returns latency and path info
whoisGETResolve IP/port → node identity + capabilities
derpmapGETCurrent DERP relay map
login-interactivePOSTInitiate interactive browser-based login
logoutPOSTLog out of current profile
startPOSTStart the daemon (resume after Stop)
shutdownPOSTGraceful daemon shutdown
watch-ipn-busGETLong-poll SSE stream of ipn.Notify events
dialPOSTProxy: dial a Tailscale peer (for SSH/outbound proxy)
cert/GETTLS certificate provisioning for tailnet domains
serve-configGET, POSTConfigure tailscale serve and Funnel
bugreportGETGenerate and upload a bug report
pprofGETExpose Go pprof profiles
metricsGETInternal Prometheus-style metrics (clientmetric)
usermetricsGETUser-facing metrics (OpenMetrics format)
goroutinesGETDump all goroutine stacks
logtapGETTap structured log stream
update/checkGET, POSTCheck for / trigger client update
suggest-exit-nodeGETAuto-suggest best exit node
set-use-exit-node-enabledPOSTEnable/disable exit node
set-dnsPOSTSet DNS record (ACME challenge response)
check-ip-forwardingGETCheck kernel IP forwarding status
dns-osconfigGETOS DNS configuration
dns-queryPOSTIssue DNS query via tailnet resolver
id-tokenGETRetrieve a Tailscale identity token (OIDC-like)
drive/sharesGET, POST, DELETETaildrive share management
drive/fileserver-addressGET, POSTSet Taildrive file server address
appc-route-infoGETApp connector route info
tka/statusGETTailnet Key Authority status
tka/initPOSTInitialize TKA
tka/signPOSTSign a node key via TKA
tka/disablePOSTDisable TKA
tka/modifyPOSTModify TKA trust anchors
tka/logGETTKA audit log
tka/affected-sigsPOSTList signatures affected by disablement
tka/generate-recovery-aumPOSTGenerate recovery AUM
tka/wrap-preauth-keyPOSTWrap a pre-auth key with TKA
policy/GETRead system policy keys
query-featurePOSTQuery feature availability from control plane
debugPOSTDebug actions (rotate keys, reauth, etc.)
debug-*GET/POSTVarious diagnostic endpoints
component-debug-loggingPOSTTemporarily enable verbose logging per component
reload-configPOSTReload declarative config file
disconnect-controlPOSTForce disconnect from coordination server
alpha-set-device-attrsPOSTSet device attributes (alpha)
set-push-device-tokenPOSTRegister push notification device token
handle-push-messagePOSTProcess an incoming push notification
set-gui-visiblePOSTNotify daemon that GUI is visible (macOS/Windows)
set-udp-gro-forwardingPOSTEnable UDP GRO forwarding

CLI#

Framework#

github.com/peterbourgon/ff/v3/ffcli — a lightweight command/flag library that wraps stdlib flag.FlagSet. Not cobra. Each command is a *ffcli.Command with a FlagSet, an Exec function, and optional subcommands.

Command structure#

Top-level commands registered in newRootCmd() (cmd/tailscale/cli/cli.go):

CommandPurpose
upConnect to tailnet (with auth key, routes, exit node flags)
downDisconnect (stop advertising, keep logged in)
setModify persistent preferences (operator, routes, SSH, DNS, etc.)
loginAuthenticate (browser or auth key)
logoutLog out
switchSwitch between multiple tailnet profiles
configurePlatform-specific configuration (kubeconfig, JetKVM, Synology, etc.)
netcheckNetwork connectivity diagnostics (DERP latency, NAT type)
ipShow Tailscale IPs for this node
dnsDNS subcommands (status, query)
statusShow node status and peer table
metricsMetrics subcommands (print)
pingPing a peer by hostname or IP
ncTCP/UDP netcat over Tailscale
sshSSH to a peer via Tailscale
funnelExpose local servers to the public internet via Funnel
serveExpose local servers to the tailnet via Serve
versionPrint version info
webStart the web UI (legacy)
fileTaildrop file transfer (cp, get)
bugreportGenerate and upload bug report
certManage TLS certificates
network-lockTailnet Key Authority management (init, sign, disable, etc.)
licensesPrint open source licenses
exit-nodeExit node management (list, suggest)
updateUpdate Tailscale client
whoisIdentify a Tailscale peer by IP or address
debugDeveloper/debug commands
driveTaildrive file sharing
id-tokenGet an identity token
configure-hostHost-level configuration (e.g. net for sysprefs)
systrayStart system tray (macOS/Windows helper)
appc-routesApp connector route inspection
waitWait until daemon is ready
syspolicyRead system policy (enterprise)

Flag patterns#

  • Flags use stdlib flag.FlagSet wrapped in ffcli. Per-command flag sets are initialized as closures: FlagSet: func() *flag.FlagSet { ... }.
  • Persistent flags are defined at the root level (--socket to override the Unix socket path).
  • No env-var flag binding at the CLI layer (env knobs are read directly via envknob in the daemon; the CLI respects TS_* vars independently).
  • The CLI guards against duplicate flag setting via the onceFlagValue wrapper applied across all commands via noDupFlagify.
  • Auto-completion supported via cmd/tailscale/cli/ffcomplete — a thin completion driver over ffcli.

Cloud Coordination REST API client (client/tailscale)#

The client/tailscale package is a Go client for the Tailscale cloud control plane at https://api.tailscale.com/api/v2/. This is the admin API used by operators and CI/CD, not the daemon-to-daemon protocol (which is handled by control/controlclient using a custom Noise+HTTPS long-poll).

Note: The package itself marks itself deprecated in favor of tailscale.com/client/tailscale/v2, but remains in the repo for internal use.

Key resource endpoints:

ResourceOperations
devicesList, Get, Delete, Authorize, Tag
keysList, Get, Create, Delete (auth keys, API keys)
routesGet, Set subnet routes for a device
dnsGet, Set nameservers, search paths, preferences
aclGet, Validate, Set tailnet ACL policy
tailnetGet tailnet-wide settings
certGet TLS certificate for a device domain

Authentication: API key or OAuth token in Authorization: Bearer header.

URL construction: BuildURL() / BuildTailnetURL() helpers — no codegen, no OpenAPI.


Library API (client/local.Client)#

The client/local package wraps all /localapi/v0/ endpoints as a typed Go library, making it the canonical way to build applications that need to inspect or control a running tailscaled. This is the primary “library API” surface.

Key method groups#

Identity & status:

  • WhoIs(ctx, remoteAddr) — resolve an IP:port to node identity
  • WhoIsNodeKey(ctx, nodeKey) — same, by WireGuard public key
  • Status(ctx) / StatusWithoutPeers(ctx) — full node/peer status
  • ProfileStatus(ctx) — list login profiles

Connectivity:

  • DialTCP(ctx, host, port) / UserDial(ctx, network, host, port) — dial Tailscale peers
  • Ping(ctx, ip, pingType) — ICMP/TSMP/DERP latency measurement

Configuration:

  • GetPrefs(ctx) / EditPrefs(ctx, maskedPrefs) — read/update preferences
  • ReloadConfig(ctx) — reload declarative config file
  • SwitchProfile / SwitchToEmptyProfile / DeleteProfile — multi-profile management

File transfer (Taildrop):

  • WaitingFiles, AwaitWaitingFiles, DeleteWaitingFile, GetWaitingFile
  • PushFile(ctx, target, size, name, reader)

Serve / Funnel: (via EditPrefs with serve config)

  • Managed through the serve-config endpoint

DNS:

  • GetDNSOSConfig, QueryDNS

TLS certificates:

  • Via cert/ endpoint (not directly exposed as a named method on Client)

Drive (Taildrive):

  • DriveSetServerAddr, DriveShareSet/Remove/Rename/List

Observability:

  • DaemonMetrics, UserMetrics, Goroutines, Pprof, BugReport, TailDaemonLogs
  • WatchIPNBus(ctx, mask) — streaming SSE watcher returning *IPNBusWatcher (iterator)
  • StreamBusEvents(ctx) — Go 1.23 iterator over raw event bus events
  • EventBusGraph, EventBusQueues

Auth:

  • StartLoginInteractive, Logout
  • IDToken(ctx, aud) — OIDC-like identity token for external service auth

Update:

  • CheckUpdate, SetUseExitNode, SuggestExitNode

API style: Typed Go methods, context.Context as first argument, returns (T, error). JSON marshaling is handled internally. No functional options; clients configure the struct fields directly.

Backward compatibility: The client/local package is used both internally and externally. The top-level package client/tailscale bears a deprecation notice pointing to v2, but the client/local package (daemon-local API) has no such notice and is maintained as a stable surface.


Web client API (client/web)#

The embedded web UI (client/web) serves a browser-based management interface. It exposes a small REST API under /api/:

EndpointMethodPurpose
/api/dataGETNode data (status, auth state)
/api/exit-nodesGETAvailable exit nodes
/api/routesPOSTUpdate subnet routes / exit node selection
/api/upPOSTInitiate tailscale up (login client mode)
/api/device-details-clickPOSTTelemetry — user viewed device details
/api/local/v0/logoutPOSTProxy logout to localapi (permission-checked)
/api/local/v0/prefsPATCHProxy prefs update to localapi (permission-checked)
/api/local/v0/update/checkGET, POSTProxy update check/trigger
/api/local/v0/update/progressPOSTProxy update progress reporting
/api/local/v0/upload-client-metricsPOSTProxy client metric upload

Authentication in the web client uses a two-layer model:

  1. CSRF protection via gorilla/csrf (token in cookie + header).
  2. Per-request peer capability check: lc.WhoIs(r.RemoteAddr) maps the requesting IP to a Tailscale node identity, then toPeerCapabilities() extracts Tailscale Access Controls (ACL) grants (e.g., ts.cap.webui:edit:exit-nodes, ts.cap.webui:edit:ssh) to gate write access.

The web client operates in three modes (ServerMode): loginClient, readonlyClient, managingClient — only the last allows mutations.


Plugin / Extension system#

Tailscale uses a feature hook model rather than a traditional plugin system:

  • feature.Hook[Func] (package tailscale.com/feature): A typed, set-once function slot. Optional modules register themselves at init() time via blank imports:
    // ssh/tailssh/tailssh.go
    ipnlocal.RegisterNewSSHServer(func(logf, lb) (SSHServer, error) { ... })
  • buildfeatures.HasXxx (package tailscale.com/feature/buildfeatures): Compile-time boolean constants (HasSSH, HasNetstack, HasDNS, HasServe, HasTaildrop, HasDebug, HasTPM, etc.). Generated as _enabled.go / _disabled.go file pairs selected by build tags. Route registration in localapi, feature behavior in ipnlocal, and CLI command inclusion all gate on these constants.

Extension points visible in the codebase:

  • SSH server: ipnlocal.RegisterNewSSHServer → registers ssh/tailssh
  • Netstack (userspace networking): hookNewNetstack feature hook → registers net/netstack
  • Web client: hookServeWebClient feature hook
  • Outbound proxy: registered via hook
  • Taildrop / file sharing: registered via hook
  • App connectors (appc): gated on buildfeatures.HasAppConnectors
  • Taildrive: gated on buildfeatures.HasTaildrive
  • Client update: gated on buildfeatures.HasClientUpdate

There is no runtime plugin loading (no hashicorp/go-plugin, no WASM, no shared library loading). All extensibility is link-time.


DERP server HTTP API (cmd/derper)#

The derper binary exposes:

  • GET /derp — WebSocket/HTTP-upgrade endpoint for the DERP relay protocol (clients connect here)
  • GET /derp/probe — Health probe (returns 200 OK)
  • GET /derp/latency-check — Latency check endpoint used by clients during path probing
  • GET /bootstrap-dns — Returns the tailscale.com DNS bootstrap data
  • GET / — HTML status page

Notable API surface observations#

  1. No versioning in Local API. All routes are /localapi/v0/ with no increment. Breaking changes are managed by feature flags and the requirement that CLI and daemon are always updated together (the version mismatch warning in local.Client handles the edge case).

  2. Route registration as a build-time API surface. The set of active endpoints depends on which build features are compiled in. This is unusual — the API surface is not fixed; it varies by binary variant. Lean container images have a significantly smaller route table than the full desktop client.

  3. The client/local.Client is the public Go interface. Third-party applications that embed Tailscale behavior (e.g., anything using tsnet-style integration) use this client. The typed methods are the de-facto stable API contract, even if no formal stability guarantee is documented.

  4. Dual REST API layers. The coordination cloud API (client/tailscale, api.tailscale.com/api/v2/) and the local daemon API (client/local, /localapi/v0/) are completely separate systems targeting different actors: the former is for admin/automation; the latter is for local GUI/CLI control.

  5. WatchIPNBus as the reactive interface. For event-driven integrations (GUI frontends, the web UI, the systray), the canonical pattern is to subscribe to watch-ipn-bus and react to ipn.Notify events rather than polling status. This is the closest Tailscale has to a pub/sub or event-stream API.