Configuring Access Control Policy
Tailscale access control lives in a single JSON policy file managed through the admin console. This file is the source of truth for your entire tailnet: who can reach what, who can SSH where, and who owns which tags. Every node in the fleet checks this policy in real time — a change takes effect immediately without restarting daemons or rotating keys.
This chapter walks through three things: inviting engineers to the tailnet, writing the groups and tag ownership declarations, and defining the SSH accept rule that grants access.
Step 1 — Invite Users to the Tailnet
Each person who needs SSH access must first join the fleet tailnet. Tailscale uses existing identity providers — there is no new account to create.
In the admin console
- Go to login.tailscale.com/admin/users
- Click Invite users
- Enter the email address associated with the engineer's Google, GitHub, or Microsoft account
- The engineer receives an invitation email and accepts it using their existing login
alice@gmail.com or bob@company.com) is what you reference in the ACL policy. Make sure you know which email they will use before writing the groups entry.
Once an engineer accepts the invitation, their devices appear in the Machines list when they install and connect Tailscale. They do not have SSH access to the Sparks yet — that requires the ACL rule in Step 3.
Onboarding vs. offboarding
| Action | How to do it | Takes effect |
|---|---|---|
| Add a new operator | Invite to tailnet + add login to group:operators in ACL | Immediately on ACL save |
| Remove an operator | Remove from tailnet (Users page) or remove from group:operators group | Immediately — all machines enforce the change |
| Temporarily suspend access | Remove their login from group:operators without removing from tailnet | Immediately — they can rejoin network but not SSH |
Step 2 — Define Groups and Tag Ownership
In the admin console, navigate to Access controls. The policy is a HuJSON file (JSON with comments allowed). The two sections to add are groups and tagOwners.
"groups": {
// Each entry is a Tailscale login (the email used to accept the invite).
// Add every engineer who should be able to SSH into the Sparks.
"group:operators": ["alice@gmail.com", "bob@company.com"]
},
"tagOwners": {
// autogroup:admin means only tailnet admins can apply this tag.
// This prevents non-admins from self-tagging their devices as servers.
"tag:spark": ["autogroup:admin"]
},
Understanding the fields
| Field | What it does |
|---|---|
groups | Named lists of Tailscale logins. Used as src in access and SSH rules. Adding a login here immediately grants whatever the group is permitted to do. |
tagOwners | Declares who may apply a given tag to machines. autogroup:admin restricts it to tailnet administrators — no one can self-assign tag:spark to a random laptop. |
tag:spark in the admin console before adding it to tagOwners, the console will reject it. Define tagOwners first, save the policy, then apply the tag to the machines (Chapter 2, Step 3).
Scaling the group over time
The group is the only place you need to change when adding or removing operators. The SSH rule (Step 3) references the group, not individual logins — so the rule never needs to be edited when team membership changes.
// Adding a third engineer: just add their login to the group.
"group:operators": [
"alice@gmail.com",
"bob@company.com",
"carol@company.com" // ← new
]
Step 3 — Write the SSH Accept Rule
The ssh section of the ACL policy defines who can SSH into what, as which Linux user. Add this block to the policy:
"ssh": [
{
"action": "accept",
"src": ["group:operators"],
"dst": ["tag:spark"],
"users": ["stoke"]
}
]
Reading the rule
Anyone ingroup:operatorsmay SSH into any machine taggedtag:spark, and they land as the Linux userstoke.
| Field | Value | Meaning |
|---|---|---|
action | "accept" | Allow the connection (as opposed to "check" for re-authentication prompts) |
src | ["group:operators"] | The Tailscale identities allowed to initiate SSH |
dst | ["tag:spark"] | The destination machines — any device carrying this tag |
users | ["stoke"] | The Linux system user to log in as on the destination |
Why tag:spark as dst is powerful
The destination is a tag, not a list of machine names or IPs. This means:
- When you add a third DGX Spark and tag it
tag:spark, it is instantly covered by this rule — no policy edit required. - When you decommission a Spark and remove the tag, it is instantly excluded.
- You can scope access more narrowly later by giving specific machines their own tag (e.g.
tag:spark-dev) and adding a separate rule for a different group.
Complete policy example
{
"groups": {
"group:operators": ["alice@gmail.com", "bob@company.com"]
},
"tagOwners": {
"tag:spark": ["autogroup:admin"]
},
"acls": [
// Default: allow operators to reach tagged Sparks on all ports
{
"action": "accept",
"src": ["group:operators"],
"dst": ["tag:spark:*"]
}
],
"ssh": [
{
"action": "accept",
"src": ["group:operators"],
"dst": ["tag:spark"],
"users": ["stoke"]
}
]
}
acls block. Tailscale denies all non-SSH traffic by default unless explicitly allowed. Add acls entries later if you need port-level access beyond SSH.
Checkpoint
After saving the policy:
- The
tag:sparktag is available in the admin console machine editor - Every login in
group:operatorsis authorized to SSH into tagged machines - Removing a login from the group immediately revokes SSH everywhere