Device management
Update a machine's Tailscale client, read its health warnings and diagnostic dumps, and edit its preferences over the control connection it holds.
The control server can ask a connected machine questions and, on a machine
whose owner allowed it, change what the client does. Everything on this
page rides the control connection the client already holds open, as a
control-to-node request: the server sends the question down the machine’s
map stream and the client posts its answer back. No inbound port, no SSH
and no agent is involved, and a machine that is not connected answers
nothing, so every operation here reports 409 on an offline machine and
504 when the client never answers.
What a client will answer depends on how it was built and what its owner
turned on. The stock tailscaled answers everything below; a client built
without its debug endpoints refuses the diagnostics with 501, and one
built without the update feature refuses the update with 500. A refusal
comes back as 502 with the client’s own words, so the message an
operator reads is the machine’s, not the server’s.
Updating a client
A machine can be told to update its own Tailscale installation. The client decides whether to obey:
- The owner must have allowed it, either with
tailscale set --auto-updateor by running the daemon withTS_ALLOW_REMOTE_UPDATE=1. Without that the client answersnot enabled. - The platform must be able to update itself. It cannot on the Mac App
Store build, which the App Store updates, and the client answers
not supportedthere. - The client refuses while it is serving inbound Tailscale SSH sessions, so an update does not cut someone’s shell off mid-command. Forcing the update overrides that.
slopscale nodes update --identifier 7
slopscale nodes update --identifier 7 --force
Through the API, GET /api/v1/node/{id}/client-update reports whether the
machine would update and whether one is already running, and POST on the
same path starts one, with {"force": true} to interrupt SSH sessions. The
read needs devices:core:read and the start devices:core; the start is
audited as node.client_update.start. POST /api/v1/nodes/client-update
takes {"nodeIds": [...], "force": false} and starts several at once, a
few at a time, answering with one result per machine so a fleet update
reports each machine on its own.
Client update notices
Separately from a push, the server tells every client whether a newer
Tailscale exists, the way the hosted control plane does: it reads the
latest stable release from pkgs.tailscale.com once a day, and a client
behind it shows Tailscale’s own update available health warning and,
with auto-update on, updates itself. The node object carries
clientVersion and updateAvailable, the server info
latestClientVersion, and the console shows the version on the machine
page with an Update available status and the latest release on the
Server page. client_updates in the configuration file
turns the lookup off or changes how often it runs.
Health and diagnostics
A machine that is connected but not working usually knows why. The client keeps the warnings it would show its own user, and the server can read them:
slopscale nodes health --identifier 7
Each warning carries a code, a severity, the title and text the client
would display, when it broke and whether the client thinks traffic is
affected. GET /api/v1/node/{id}/health returns the same, ordered by code,
and needs devices:core:read.
For anything the warnings do not explain, the client will hand over the dumps it keeps for support:
| Kind | What it is |
|---|---|
prefs |
The preferences the client holds, as JSON |
netmap |
The client’s current network map, with its keys redacted |
metrics |
The client’s counters in Prometheus text format |
goroutines |
A scrubbed goroutine dump |
sockstats |
The client’s socket statistics |
tka-log |
The tailnet lock log the machine holds |
slopscale nodes diagnostics --identifier 7 --kind netmap --out netmap.json
slopscale nodes diagnostics --identifier 7 --kind goroutines
GET /api/v1/node/{id}/diagnostics/{kind} answers with the dump as the
client wrote it, with the client’s content type and a filename, so it is a
download rather than a JSON body. It needs devices:core, not the read
scope, because a netmap and a preferences dump describe the machine’s whole
view of the tailnet; every download is audited as node.diagnostics.read
with the kind.
Managed preferences
The preferences a machine’s owner set are readable on any client with its debug endpoints:
slopscale nodes prefs get --identifier 7
Changing them is a different matter, and needs the machine’s consent. Tailscale’s usual model is per-feature double opt-in: the tailnet admin can ask for something and the machine’s owner still agrees to it, one setting at a time. Remote configuration replaces that with a single switch on the machine. The client’s own documentation is blunt about what it means:
Someone with a shell on the machine turns it on:
tailscale set --remote-config
The client then reports the opt-in in its Hostinfo, which the machine’s
record shows as remoteConfig, and the server will edit these
preferences. It needs a client from Tailscale 1.102 or later, which is where
the proxy the edit rides on appeared; an older client refuses whatever its
preferences say. Without the opt-in the API answers 409 and tells the
operator to run the command above.
| Preference | What it does |
|---|---|
advertiseRoutes |
The subnets the machine offers to the tailnet |
advertiseExitNode |
Whether it offers to be an exit node |
acceptRoutes |
Whether it uses the subnets other machines advertise |
acceptDns |
Whether it uses the tailnet’s DNS configuration |
exitNode |
The exit node it uses, by stable ID or address; empty clears |
exitNodeAllowLanAccess |
Whether the local network stays reachable through an exit node |
runSsh |
Whether it runs Tailscale SSH |
shieldsUp |
Whether it blocks incoming connections |
hostname |
The name it reports |
autoUpdateCheck |
Whether it checks for client updates |
autoUpdateApply |
Whether it applies them |
advertiseConnector |
Whether it offers to be an app connector |
postureChecking |
Whether it reports device posture |
Only the preferences named in a request are changed; the rest keep
whatever the machine’s owner set. Offering to be an exit node is stored
inside the advertised routes as the two default routes, so the server
recomputes both together the way tailscale set does: turning it on leaves
the other subnets alone.
slopscale nodes prefs set --identifier 7 --accept-routes=true
slopscale nodes prefs set --identifier 7 --advertise-routes 10.0.0.0/24,10.1.0.0/24
slopscale nodes prefs set --identifier 7 --advertise-exit-node=true
PATCH /api/v1/node/{id}/preferences takes the same fields as JSON, needs
devices:core and is audited as node.preferences.update with the names
of the preferences the request changed. GET on the same path reads them
and needs devices:core:read.
What else a machine will tell the server
Three smaller questions round the set out.
SSH username hints. A client will suggest the logins it would accept
for a Tailscale SSH session, which is what the console’s in-browser
terminal offers in its username box. The hints are only that: the SSH
policy still decides who may log in as whom, and a machine with many users
truncates the list. GET /api/v1/node/{id}/ssh-usernames declares no
scope, because it follows the same visibility rule as
browser SSH: whoever may open a session to the machine may
read its hints, so a member sees their own machines and the ones their
machines can already reach.
App connector routes. An app connector learns addresses as
it resolves the domains it answers for. GET /api/v1/node/{id}/app-connector-routes reports what it has learned, domain
by domain, which is how an operator checks a connector without reading its
logs. A machine that is not a connector answers with none. It needs
devices:core:read.
TLS certificate status. A machine that serves HTTPS through
Serve or Funnel caches a certificate for its MagicDNS name,
and renewal fails quietly. GET /api/v1/node/{id}/tls-cert reports whether
the certificate is valid, missing or expired and the error the client last
saw. It needs devices:core:read.