Taking Control of My Tailnet: How I Use Tailscale ACLs and Tags to Organize My Devices

When I started using Tailscale, it was great to link all my machines—laptops, servers, Raspberry Pis, containers, and exit nodes—into one private network. As the number of devices grew, I needed a simple way to keep things secure and easy to manage. That’s why I turned to Tailscale’s Access Control Lists (ACLs) and tags.

Why I Use ACLs and Tags

By default, Tailscale lets any node talk to any other node. That works for a few devices, but when I added services and shared with others, I wanted:

Tags let me label devices—server, container, exit-node, rdp-client, and so on—so each machine has a clear role. Then ACLs use those tags to allow only the right traffic. This makes it easy to understand exactly what each device is allowed to do, simplifying troubleshooting and improving security auditing.

ACL File Structure

My policy file lives in a private Git repository. At the top, I set up my admin group, tag owners, and host names:

{
  "groups": {
    "group:root": [ /* Admin group with full access */ ]
  },

  "tagOwners": {
    /* All device role tags are owned and assigned by the admin group */
  },

  "hosts": {
    /* Static hostnames mapped to internal Tailscale IP addresses */
    "arkg15.perch-map.ts.net":     "fd7a:115c:a1e0::cafe:babe",
    "arklab.perch-map.ts.net":     "fd7a:115c:a1e0::dead:beef",
    "arklab-wsl.perch-map.ts.net": "fd7a:115c:a1e0::face:feed"
  },

  "acls": [ /* Access control rules defined below */ ],
  
  "ssh":  [ /* SSH access rules using Tailscale SSH */ ],
  
  "nodeAttrs": [
    /* Node attributes like Taildrive and Funnel capabilities */
  ],
  
  "grants": [
    /* Permissions for Web UI and Drive access */
  ]
}

How I Tag My Devices

  1. Core Servers

  2. Containers & Labs

  3. Remote Desktop

  4. Exit Nodes & DNS

When I add a new device, I tag it immediately—either via the admin console or an auth key that tags devices automatically. This reduces the risk of misconfiguration and ensures consistent security policies.

My ACL Rules

Here’s the core of my ACL setup:

"acls": [
  {
   "action": "accept",
   "src":    ["group:root"],
   "dst":    ["*:*"],
  },

  {
   "action": "accept",
   "src":    ["group:root", "tag:rdp-client"],
   "dst":    ["tag:rdp-server:3389"],
  },

  {
   "action": "accept",
   "src":    ["arklab.perch-map.ts.net"],
   "dst":    ["arkg15.perch-map.ts.net:3389"],
  },

  {
   "action": "accept",
   "src":    ["group:root", "tag:exit-node-user"],
   "dst":    ["autogroup:internet:*"],
  },

  {
   "action": "accept",
   "src":    ["group:root", "tag:dns-client"],
   "dst":    ["tag:dns-server:53"],
  },

  {
   "action": "accept",
   "src":    ["group:root", "tag:doh-client"],
   "dst":    ["tag:dns-server:443", "tag:dns-server:60443"],
  },

  {
   "action": "accept",
   "src":    ["group:root", "tag:dot-client"],
   "dst":    ["tag:dns-server:853", "tag:dns-server:60853"],
  },
]

Each rule clearly defines allowed interactions. By explicitly stating ports and allowed sources, I can quickly identify unnecessary permissions or diagnose connectivity issues. This granular control minimizes potential vulnerabilities due to overly permissive access.

Securing SSH with Tailscale SSH

I use Tailscale SSH instead of manually managing SSH keys. Here’s my SSH setup:

"ssh": [
  {
   "action":      "check",
   "src":         ["group:root"],
   "dst":         ["autogroup:self"],
   "users":       ["autogroup:nonroot", "root"],
   "checkPeriod": "72h",
  },

  {
   "action": "accept",
   "src":    ["group:root"],
   "dst":    ["tag:device", "tag:server", "tag:container"],
   "users":  ["autogroup:nonroot"],
  },

  {
   "action":      "check",
   "src":         ["group:root"],
   "dst":         ["tag:device", "tag:server", "tag:container"],
   "users":       ["root"],
   "checkPeriod": "72h",
  },

  {
   "action": "accept",
   "src":    ["group:root", "tag:tailscale-ssh-client"],
   "dst":    ["tag:tailscale-ssh-server"],
   "users":  ["autogroup:nonroot", "root"],
  },
]

Now running ssh [email protected] just works—Tailscale checks the policy, lets me in, and there is no need for manually configuring a list of authorized_keys anymore, as the burden of authentication and authorization falls to Tailscale.

Using tags and ACLs has made my tailnet safe and easy to manage. Give it a try—you’ll save time and avoid surprises when you add new devices or share access with others.