TailscaleGuide
Isometric white padlock above a dark grid of glowing pink network nodes linked by arrows, depicting access control rules between devices on a private network.
Networking

Tailscale ACL Examples Explained: 8 Policies That Work

Eight Tailscale ACL examples explained line by line: allow-all, deny-all, own devices, tags, groups, subnets, exit nodes, SSH, and policy tests.

By TailscaleGuide Editorial · · 7 min read

Tailscale ACL examples explained in one sentence: every rule is an accept from a src to a dst of host:port, and anything not accepted is dropped. Eight patterns cover almost every home or small-office tailnet: allow-all, deny-all, own-devices, tags, groups with ports, subnet CIDRs, exit nodes and SSH, each locked in with tests.

Tailscale now recommends writing new rules as grants; the conversion is mechanical and covered at the end. The examples use ACL syntax because most existing policy files, and Tailscale’s own ACL policy examples, still do.

How does a Tailscale ACL rule work?

A Tailscale ACL rule is a one-way permit: src may open connections to dst on the listed ports, optionally limited by proto. There is no deny action, so the policy is the union of every accept and rule order never matters. Nothing can shadow anything else. Anything no rule matches is dropped.

{
  "action": "accept",              // the only valid value
  "src":    ["group:admins"],      // who opens the connection
  "proto":  "tcp",                 // optional
  "dst":    ["tag:server:22,443"]  // host:port
}

Tailscale’s ACL documentation sums up the model as “deny-by-default, directional, locally enforced, and don’t affect local network traffic.” Directional means a rule letting your laptop reach the NAS does not let the NAS open connections back; replies to your sessions still flow. Locally enforced means each device applies the filter itself.

The policy file syntax reference lists what src and dst accept: login names, group:, tag:, autogroups, Tailscale IPs, CIDRs, hosts aliases and *. Ports are *, 22, 80,443 or a range like 1000-2000. The file is HuJSON, so comments and trailing commas are legal.

Watch the wildcard: per the targets and selectors reference, * expands to “all Tailscale IP addresses and all IP addresses from approved subnet routes.” How a Tailscale mesh VPN works covers how each node receives this filter.

Example 1: the default allow-all policy

Tailscale’s examples page lists this as the default tailnet policy:

{
  "acls": [
    { "action": "accept", "src": ["*"], "dst": ["*:*"] }
  ],
  "ssh": [
    {
      "action": "check",
      "src": ["autogroup:member"],
      "dst": ["autogroup:self"],
      "users": ["autogroup:nonroot", "root"]
    }
  ]
}

The acls rule lets every device reach every port on every other device. Because * includes approved subnet routes, it also hands every node, including the tablet in a drawer, a path to every LAN segment a subnet router advertises. The ssh block lets each member SSH into their own devices as a non-root user or root, in check mode, which demands a recent browser re-authentication.

That is a reasonable policy for two laptops. It stops being one the day you approve a route to your management VLAN, which is why replacing it is a step in setting up your first tailnet.

Example 2: deny all

{ "acls": [] }

An empty acls array permits nothing, including subnet routes and exit nodes, though devices stay authenticated. Use it as an incident lockdown or as the blank page you build up from. If you push it as a lockdown, schedule the restore for the same day. Blanket blocks train people to route around the VPN.

Example 3: users reach their own devices

{
  "acls": [
    { "action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self:*"] }
  ]
}

autogroup:member is every direct member of the tailnet. autogroup:self resolves, per user, to the devices that same user owns. You reach your devices, your partner reaches theirs, nobody crosses over.

Two restrictions catch people. autogroup:self is destination-only, and the source must be users, groups or role-based autogroups. And per the syntax reference it “only applies to user-owned devices. It does not apply to tagged devices.” The moment you tag your NAS, this rule stops covering it.

Example 4: tags for servers

{
  "tagOwners": {
    "tag:server": ["autogroup:admin"],
    "tag:nas":    ["autogroup:admin"],
    "tag:media":  ["autogroup:admin"]
  },
  "acls": [
    { "action": "accept", "src": ["tag:media"], "dst": ["tag:nas:445,2049"] }
  ]
}

Tags give a machine an identity that is not a person. tagOwners controls who may apply each tag; here, only admins. The rule lets the media server reach the NAS on SMB (445) and NFS (2049) and nothing else, so a compromised media container cannot SSH into the NAS. Apply the tag when bringing the node up:

sudo tailscale up --advertise-tags=tag:nas

Per Tailscale’s tags documentation, “Applying a tag to a device removes any user-based authentication,” and a newly tagged device has key expiry disabled by default. That is right for an unattended box, and a reason to keep each tag’s permissions narrow: a tagged node stays authorized until someone removes it.

Example 5: groups and specific ports

{
  "groups": {
    "group:admins": ["YOUR_LOGIN"],                // login names from the Users page
    "group:family": ["PARTNER_LOGIN", "KID_LOGIN"]
  },
  "acls": [
    { "action": "accept", "src": ["group:admins"], "dst": ["tag:server:22,443"] },
    { "action": "accept", "src": ["group:family"], "dst": ["tag:media:443"] },
    { "action": "accept", "src": ["autogroup:member"], "dst": ["tag:dns:53"] }
  ]
}

Groups are named lists of users; replace the placeholders with the login names shown in the admin console. Admins get SSH and HTTPS on servers, family gets the media server’s HTTPS front end, everyone gets DNS.

The DNS rule deliberately omits proto. A "proto": "udp" rule looks tidier and breaks lookups: RFC 7766, DNS Transport over TCP - Implementation Requirements, makes TCP support mandatory, and truncated answers retry over TCP 53, so a UDP-only rule fails only on large responses. If you do use proto, clients need Tailscale v1.18.2 or later.

Example 6: subnet routes and host aliases

{
  "hosts": {
    "mgmt-vlan": "192.168.10.0/24",   // switch, AP and IPMI management
    "nas-lan":   "192.168.40.10"
  },
  "acls": [
    { "action": "accept", "src": ["group:admins"], "dst": ["mgmt-vlan:22,443"] },
    { "action": "accept", "src": ["group:family"], "dst": ["nas-lan:445"] }
  ]
}

CIDR and IP destinations police traffic through a subnet router to hardware that cannot run a client, such as a USW-Pro-24-PoE management page or an IPMI board. hosts aliases keep the rules readable.

Route approval and the ACL are separate controls: approval decides whether the route exists, the ACL decides who uses it on which ports, and under the default *:* an approved /24 is open to every node. Forwarding, autoApprovers, SNAT and MSS clamping are in our Tailscale subnet router configuration guide.

Example 7: exit node access

{
  "acls": [
    { "action": "accept", "src": ["group:family"], "dst": ["autogroup:internet:*"] }
  ]
}

The default policy lets any user use an exit node. A custom policy does not, unless a rule grants autogroup:internet. The usual mistake, called out in Tailscale’s exit node documentation, is putting the exit node device itself in dst: that “only permits connections to the exit node, such as SSH. It does not permit using the device as an internet gateway.”

autogroup:internet is destination-only and covers public address space, excluding private, CGNAT and link-local ranges. Pinning a group to one specific exit node is a grants feature, the via field. Advertising and approving the node itself is covered in Tailscale exit node setup.

Example 8: Tailscale SSH rules

{
  "acls": [
    { "action": "accept", "src": ["group:admins"], "dst": ["tag:server:22"] }
  ],
  "ssh": [
    {
      "action": "check",
      "src": ["group:admins"],
      "dst": ["tag:server"],
      "users": ["autogroup:nonroot", "root"]
    }
  ]
}

Tailscale SSH needs two permissions, and people write only the second. The Tailscale SSH documentation requires the policy to permit “both network access and SSH access”: the acls rule to port 22, and the ssh rule naming which local users the source may log in as.

check forces browser re-authentication, by default every 12 hours; accept skips it. Enable the server side with sudo tailscale set --ssh. Tagged devices can SSH only into other tagged devices, never into user-owned ones, and an SSH rule from a tagged source cannot use check mode.

How do Tailscale ACL tests work?

Tests are assertions in the policy file that Tailscale evaluates whenever the file changes. Per the syntax reference, “If an assertion fails, Tailscale rejects the updated tailnet policy file with an error.” Write an accept and a deny list for each source that matters and a bad edit never reaches your devices.

"tests": [
  {
    "src": "tag:media",
    "accept": ["tag:nas:445"],
    "deny":   ["tag:nas:22", "tag:server:443"]
  },
  {
    "src": "group:family",
    "accept": ["tag:media:443", "192.168.40.10:445"],
    "deny":   ["tag:server:22", "192.168.10.1:443"]
  }
]

Test sources and destinations must name specific entities; * is not allowed. A matching sshTests section does the same for SSH rules. The tests above fail against Example 1, which is the point: anyone who pastes allow-all back in gets an error instead of an open management VLAN.

Then confirm from real devices:

nc -vz -w 3 nas 22             # from tag:media: expect failure
nc -vz -w 3 192.168.10.1 443   # from a family laptop: expect failure

Should you write ACLs or grants?

Write grants for new policies. Tailscale made grants generally available on May 29, 2025 and recommends them, while its migration guide says ACLs “will continue to function indefinitely.” Both syntaxes coexist in one policy file, and the admin console’s Convert to grants button rewrites an entire acls section.

The mapping is mechanical: drop action, split the port off dst into an ip array, and fold any proto into it as a prefix such as tcp:443. Example 4 becomes:

"grants": [
  { "src": ["tag:media"], "dst": ["tag:nas"], "ip": ["445", "2049"] }
]

One trap: an ACL whose dst lists two tags with different ports must become two grants. Collapsing tag:web-server:80 and tag:dev-server:22 into one grant with "ip": ["80", "22"] opens both ports on both tags. Grants also add via for routing control and app for application capabilities, neither of which the ACL syntax can express.

FAQ

Does the order of rules matter in a Tailscale ACL?

No. Tailscale ACLs have a single action, accept, so no earlier rule can shadow or override a later one. The effective policy is the union of every accept rule and grant, applied on top of deny-by-default. Order rules for readability instead, grouped by destination, so policy reviews and Git diffs stay legible.

How do I block a device in a Tailscale ACL?

You block a device by not granting it anything. There is no deny action, so remove or narrow every rule whose src or dst matches it, including * and broad autogroups like autogroup:member. For an immediate cut-off, remove the device from the admin console’s Machines page first, then fix the policy.

Do Tailscale ACLs block traffic on my local network?

No. Tailscale documents ACLs as deny-by-default, directional, locally enforced, and not affecting local network traffic. A laptop and a NAS on the same VLAN still talk over the LAN whatever the policy says. Isolation between VLANs remains the router’s job, whether that is a UDM Pro, an OPNsense box or a MikroTik.

Where do I edit the Tailscale ACL policy file?

Edit it on the Access controls page of the Tailscale admin console, which offers both a JSON editor and a visual editor. Tailscale also supports managing the file through GitOps and the Tailscale API. Once the policy protects anything real, GitOps is the better choice: every change gets a diff, a review and a revert.

Sources

  1. ACL policy examples
  2. Syntax reference for the tailnet policy file
  3. Manage permissions using ACLs
  4. Targets and selectors
  5. Group devices with tags
  6. Exit nodes (route all traffic)
  7. Tailscale SSH
  8. Migrate from ACLs to grants
  9. A new generation of Tailscale access controls
  10. RFC 7766: DNS Transport over TCP - Implementation Requirements
#tailscale #acl #grants#access-control#tailscale-ssh

Related