Network access controls (ACLs)

Tailscale supports network access control rules, sometimes called ACLs. ACLs let you precisely define what a particular user or device is permitted to access on your Tailscale network.


Introduction

When you first create your network, it gets initialized with an “allow all” access policy, which permits all devices that are signed into your network to access all other devices. When you’re ready to restrict access further, you can do so by editing the access rules in the admin console, or through the Tailscale API.

Tailscale access rules are “default deny”, meaning that a communication between two devices can only proceed if an access rule explicitly allows it, otherwise communication is denied. This is why new networks are initialized with an explicit “allow all” policy, otherwise Tailscale would not let you access anything out of the box.

Access rules can make use of groups to grant access to many users with few rules. Similarly, you can use tags to assign purpose-based identities to devices to further scale up your policies. Combined, groups and tags let you build powerful role-based access control (RBAC) policies. Depending on your definition, server tagging could also be considered a form of attribute-based access control (ABAC).

The access rules you define for your network get distributed to all the devices in your network, and enforcement of the rules happens on each device directly, without further involvement from Tailscale’s servers.

If you’d like to learn more about Tailscale’s approach to access control in general, we’ve written a detailed blog post that goes deep into the history of access control systems, and why we designed Tailscale’s access rules the way we did.

Tailscale policy syntax

Tailscale access control rules are expressed as a single “human JSON” tailnet policy file. HuJSON is a superset of JSON that allows comments and trailing commas. This makes a tailnet policy file easy to maintain while staying read/writable by both humans and machines.

For simple cases, you can edit the tailnet policy file by hand in the admin panel. Organizations with more complex needs can use the API to automatically update rules from software.

The tailnet policy file has several top-level sections relating to ACLs, which we explore in detail below:

  • acls, the access rules themselves.
  • groups, collections of users, to avoid repeating yourself in access rules.
  • hosts, human readable shorthands for IP addresses.
  • tests, which let you check the behavior of ACLs and avoid accidentally breaking an important permission.
  • tagOwners, to define which users can assign tags to a device.
  • autoApprovers, to define which users can advertise routes or exit nodes without further approval.
  • ssh, to define which users can establish a Tailscale SSH connection on which devices, and as which SSH users.
  • nodeAttrs, to define which devices can use certain features.

The tailnet policy file also contains network-wide policy settings unrelated to access control: derpMap, disableIPv4, and randomizeClientPort are documented in the Network policy options section of this article, for completeness.

An example tailnet policy file:

{
  "acls": [
    // dave@tailscale.com can access everything on host vega (defined below)
    {
      "action": "accept",
      "src": ["dave@tailscale.com"],
      "dst": ["vega:*"],
    },
    // The security group can access all HTTPS servers, and everything on
    // the corp network.
    {
      "action": "accept",
      "src": ["group:security"],
      "dst": [
        "*:443",
        "corp-network:*"
      ],
    },
    // The employees group can access web apps over HTTP.
    {
      "action": "accept",
      "src": ["group:employees"],
      "proto": "tcp",
      "dst": ["tag:webapps:80"],
    },
    // Only allow IP protocol 16 (CHAOS) to security
    {
      "action": "accept",
      "src": ["group:employees"],
      "proto": "16",
      "dst": ["group:security:*"],
    },
    // Both dave@tailscale.com and the security group can access lab devices.
    {
      "action": "accept",
      "src": ["dave@tailscale.com", "group:security"],
      "dst": ["tag:lab:*"],
    },
    // All lab devices can talk to each other, but nothing else.
    {
      "action": "accept",
      "src": ["tag:lab"],
      "dst": ["tag:lab:*"],
    },
    // Devices authenticated as the same user can talk to each other on port 22.
    {
      "action": "accept",
      "src": ["*"],
      "dst": ["autogroup:self:22"],
    },
  ],
  // Readable shorthands for devices and networks.
  "hosts": {
    "vega": "100.3.4.5",
    "corp-network": "10.0.0.0/8",
    "critical-corp-server": "10.0.0.1",
  },
  // Role-based groups of users.
  "groups": {
    "group:security": [
      "laura@tailscale.com",
      "daniel@tailscale.com",
    ],
    "group:employees": [
      "dave@tailscale.com",
      "laura@tailscale.com",
      "daniel@tailscale.com",
      "alice@tailscale.com",
      "bob@tailscale.com",
    ],
  },
  "tests": [
    // Security can always access HTTPS on vega, but not HTTP.
    {
      "src": "group:security",
      "accept": ["vega:443"],
      "deny": ["vega:80"],
    },
    // dave@tailscale.com can SSH into vega and the lab.
    {
      "src": "dave@tailscale.com",
      "accept": ["vega:22", "tag:lab:22"],
    },
    // Lab devices can never access a critical corp service.
    {
      "src": "tag:lab",
      "deny": ["critical-corp-server:80"],
    },
  ],
  "tagOwners": {
    "tag:lab": ["dave@tailscale.com"],
    "tag:security": ["group:security"],
    "tag:webapps": ["dave@tailscale.com"]
  },
  "autoApprovers": {
    // Alice can create subnet routers advertising routes in 10.0.0.0/24 that are auto-approved
    "routes": {
      "10.0.0.0/24": ["alice@tailscale.com"],
    },
    // A device tagged security can advertise exit nodes that are auto-approved
    "exitNode": ["tag:security"],
  },
  "ssh": [
    {
      // All users can SSH to their own devices, as non-root
      "action": "accept",
      "src": ["autogroup:members"],
      "dst": ["autogroup:self"],
      "users": ["autogroup:nonroot"]
    }
  ],
  "derpMap": {
    "regions": {
      "900": {
        "regionID": 900,
        "hostName": "custom-derp.example.com",
      },
    },
  },
  "disableIPv4": false,
  "randomizeClientPort": false,
}

Previewing changes

When editing the tailnet policy file in the admin console, you can preview the permissions granted to your users.

To do so, navigate to the Preview rules tab and select a user from the dropdown. A list of destinations (one per line) accessible to the specified user is shown, as well the line number that defines that rule. It also includes any other users/groups that can access that destination due to that rule.

Another way to increase confidence in your changes is to define some ACL tests, to check that your changes don’t accidentally remove access to an important system, or accidentally open up access wider than you intended.

ACL rules

The acls section of the tailnet policy file is a list of access rules for your network. Each rule is a HuJSON object that grants access from a set of sources to a set of destinations.

All rules eventually boil down to allowing traffic from a particular source IP address to a destination IP address and port. While you can write rules that reference IP addresses directly, it’s more common to use higher-level specifiers like usernames and groups, which Tailscale automatically translates to the right low-level rules.

Each access rule looks like this:

{
  "action": "accept",
  "src": [ list-of-sources... ],
  "proto": "protocol", // optional
  "dst": [ list-of-destinations... ],
}
Previously we used users and ports, instead of src and dst, respectively. While both sets of terms are currently supported, we are using src and dst going forward.

Tailscale access rules are “default deny”, so the only possible action is accept, to allow traffic that would otherwise be denied.

The src field specifies a list of sources to which the rule applies. Each element in the list can be one of the following:

Type Example Meaning
Any * No restriction on the source
User shreya@example.com All devices currently signed in as the given user
Group (ref) group:example Same as listing every user in the group explicitly
Tailscale IP 100.101.102.103 Only the device that owns the given Tailscale IP
Subnet CIDR Range (ref) 192.168.1.0/24 Any IP within the given subnet
Hosts (ref) my-host Looks up the Tailscale IP or CIDR in the hosts section
Tags (ref) tag:production All devices currently tagged with the given tag
Shared users (ref) autogroup:shared Devices of users who accepted a sharing invitation for the destination
Tailnet members autogroup:members Devices of users who are a direct member (not a shared user) of the tailnet

The proto field is optional, and specifies the protocol to which the rule applies. (If no protocol is specified, then the ACL rule applies to all TCP and UDP traffic.)

A proto field can only be included in an ACL rule for Tailscale version v1.18.2 or later. Older clients will fail closed, blocking traffic for ACL rules with protocols, unless it is otherwise allowed.

If traffic is allowed for a given pair of IPs, then ICMP will also be allowed.

proto can be specified as either an IANA IP protocol number 1-255 (e.g., "16") or one of the following named aliases (e.g., "sctp"):

Protocol proto IANA protocol number
Internet Group Management (IGMP) igmp 2
IPv4 encapsulation ipv4, ip-in-ip 4
Transmission Control (TCP) tcp 6
Exterior Gateway Protocol (EGP) egp 8
Any private interior gateway igp 9
User Datagram (UDP) udp 17
Generic Routing Encapsulation (GRE) gre 47
Encap Security Payload (ESP) esp 50
Authentication Header (AH) ah 51
Stream Control Transmission Protocol (SCTP) sctp 132

Only TCP, UDP, and SCTP traffic support specifying ports. For all other protocols, the port specified needs to be *.

The dst field specifies a list of destination devices and ports to which the rule applies. Each element in the list is of the form host:ports. host is one of the following:

Type Example Meaning
Any * No restriction on the destination
User shreya@example.com Any device currently signed in as the given user
Group (ref) group:example Same as listing every user in the group explicitly
Tailscale IP 100.101.102.103 Only the device that owns the given Tailscale IP
Hosts (ref) my-host Looks up the Tailscale IP in the hosts section
Subnet CIDR Range (ref) 192.168.1.0/24 Any IP within the given subnet
Tags (ref) tag:production Any device currently tagged with the given tag
Internet access (ref) autogroup:internet Access to the internet through exit nodes
Own devices autogroup:self Access to devices where the same user is authenticated on both the src and the dst. This does not include devices the user has ACL tags.
Tailnet devices autogroup:members Access to devices on the tailnet where the user is a direct member (not a shared user) of the tailnet

ports is one of the following:

Type Example
Any *
Single 22
Multiple 80,443
Range 1000-2000

Users

Users can be specified in both the source (src) and destination (dst) fields of an access rule. Users must be referenced using the full email address they use to sign into Tailscale.

If you sign into your network with GitHub, use the form username@github. For example, the GitHub user alice should be written as alice@github.

Referencing an external user in an access rule does not implicitly grant them access to your Tailscale network. However, if you’ve shared a device with someone outside your Tailscale network, you can use their Tailscale login email in ACLs to further restrict what they can access on the shared device.

Groups

The groups section lets you define a shorthand for a group of users, which you can then use in ACL rules instead of listing users out explicitly. Any change you make to the membership of a group is automatically propagated to all the rules that reference that group.

A groups definition looks like this:

"groups": {
  "group:engineering": [
    "dave@example.com",
    "laura@example.com",
  ],
  "group:sales": [
    "brad@example.com",
    "alice@example.com",
  ],
},

Every group name must start with the prefix group:. Each member of a group is specified by their full email address, as explained in the users section above. To avoid the risk of obfuscating group membership, groups cannot contain other groups.

Autogroups

An autogroup is a special group that automatically includes users, destinations, or usernames with the same properties, which you can then use in access rules.

Autogroup Where allowed Meaning
autogroup:internet As a dst Used to allow access for any user through any exit node in your tailnet. You cannot restrict access to only some exit nodes.
autogroup:members As a src or dst Used to allow access for any user who is a direct member (not a shared user) of the tailnet.
autogroup:nonroot As an SSH user Used to allow Tailscale SSH access to any user that is not root.
autogroup:self As a dst Used to allow access for any user that is authenticated as the same user as the source.
autogroup:shared As a src Used to allow access for any user who accepted a sharing invitation to your network. This lets you write rules without knowing sharee email addresses in advance.

Here’s an example ssh rule that allows all users to have Tailscale SSH access to their own devices, as non-root:

  "ssh": [
    {
      // All users can SSH to their own devices, as non-root
      "action": "accept",
      "src": ["autogroup:members"],
      "dst": ["autogroup:self"],
      "users": ["autogroup:nonroot"]
    }

Important note about autogroup:nonroot

In the default ACL, the ssh rule uses autogroup:self for the dst field and autogroup:nonroot in the users field. If you change the dst field from autogroup:self to some other destination, such as an ACL tag, also consider replacing autogroup:nonroot in the users field. If you don't remove autogroup:nonroot from the users field, then anyone permitted by the src setting will be able to SSH in as any nonroot user on the dst device.

Hosts

The hosts section lets you define a human-friendly name for an IP address or IP range, to make access rules more readable.

A hosts definition looks like this:

"hosts": {
  "example-host-1": "100.100.100.100",
  "example-network-1": "100.100.101.100/24",
},

The human-friendly name cannot include the character @.

Tests

The tests section lets you write assertions about your access rules, which are checked whenever the tailnet policy file is changed. Failing assertions cause the new tailnet policy file to be rejected, with an error detailing which test assertion wasn’t met.

Just like writing tests for software, ACL tests let you ensure that an important permission isn’t accidentally revoked later on, or that a critical system in your network isn’t exposed more than expected.

A tests definition looks like this:

"tests": [
  {
    "src": "dave@example.com",
    "accept": ["example-host-1:22", "vega:80"],
    "deny": ["1.2.3.4:443"],
  },
],

The src field specifies the user identity being tested, which can be a user’s email address, a group, an ACL tag, or a host that maps to an IP address. The test case will be run from the perspective of a device signed in as that user, with that group membership, with that tag, or as that host, respectively.

The accept and deny fields specify destinations that the ACL rules should accept or deny, respectively.

Previously we used allow, instead of accept, in ACL tests. While both terms are currently supported, we are using accept going forward.

Each destination in the list is of the form host:port, where port is a single numeric port and host is one of the following:

Type Example Meaning
Tailscale IP 100.101.102.103
Hosts (ref) my-host Looks up the Tailscale IP in the hosts section
User shreya@example.com The Tailscale IP of a device currently signed in as the given user
Group group:security@example.com The Tailscale IP of a device currently signed in as a representative member of the given group
Tags (ref) tag:production The Tailscale IP of a device currently tagged with the given tag

Protocols cannot yet be included in ACL tests.

Tag Owners

The tagOwners section of the tailnet policy file defines the tags that can be applied to devices, and the list of users who are allowed to assign each tag.

Tags are a substitute for a human identity on a device. That is, rather than a device being authenticated as a particular user, the device’s identity on the Tailscale network is the set of tags assigned to it. You can learn more in our article on server role accounts with ACL tags.

A tagOwners definition looks like this:

"tagOwners": {
  "tag:webserver": [
    "group:engineering",
  ],
  "tag:secure-server": [
    "group:security-admins",
    "president@example.com",
  ],
  "tag:corp": [
    "autogroup:members",
  ],
}

Every tag name must start with the prefix tag:. Each owner of a tag can be a user’s full login email address (as defined in the users section above), a group name, or autogroup:members.

Auto Approvers for routes and exit nodes

Auto Approvers is currently in beta.

The autoApprovers section of the tailnet policy file defines the list of users who can perform certain actions without requiring further approval from the admin console. This includes advertising a specified set of routes as a subnet router or advertising an exit node. For routes, this also permits the auto approvers to advertise a subnet of the specified routes.

An Owner, Admin or Network Admin can still disable the route or exit node from the admin console. To avoid the same route being advertised and auto-approved again, consider modifying autoApprovers.

If the device is re-authenticated by a different user who cannot advertise the route or exit node, or the user who advertised it is suspended or deleted, the route or exit node is no longer advertised. To avoid this, consider making an ACL tag an auto approver.

An autoApprovers definition looks like this:

"autoApprovers": {
  "routes": {
    "10.0.0.0/24": ["group:engineering", "alice@example.com", "tag:foo"],
  },
  "exitNode": ["tag:bar"],
}

The auto approver of a route or exit node can be a user’s full login email address (as defined in the users section above), a group name, or a tag.

Tailscale SSH

Tailscale SSH is currently in beta.

The ssh section of the tailnet policy file defines the lists of users and devices that can use Tailscale SSH, and as which SSH users. For a connection to be permitted, the tailnet policy file must contain rules permitting both network access and SSH access:

  1. An access rule to allow connections from the source to the destination on port 22.
  2. An SSH access rule to allow connections from the source to the destination and the given SSH users. This is used for Tailscale SSH, to distribute keys for authenticating SSH connections.

An ssh definition looks like this:

{
  "action": "check", // "accept" or "check"
  "src": [ list-of-sources... ],
  "dst": [ list-of-destinations... ],
  "users": [ list-of-ssh-users... ],
  "checkPeriod": "20h", // optional, only for check actions. default 12h
},

The action field specifies whether to accept the connection or to perform additional checks on it.

The src field is the source where a connection originates from. This can be a user, group, or tag.

The dst field is the destination where the connection goes. This can be a user or tag. Note that unlike ACLs, a port cannot be specified. Only port 22 is allowed, and does not need to be specified as it is used by default.

The users field is the set of allowed usernames on the host. An SSH rule can also specify autogroup:nonroot to allow any user that is not root. If no user is specified, Tailscale will use the local host’s user. That is, if you are logged in as alice locally, then SSH to another device, Tailscale SSH will try to log in as user alice. Like other SSH clients, Tailscale will only use user accounts that already exist on the host, not create new accounts.

Optionally, for check mode only, the checkPeriod field is the time period for which to allow a connection before requiring a check. This can be specified in minutes or hours, with a minimum of one minute and a maximum of 168 hours (one week). If not specified, this is 12 hours.

SSH access rules are evaluated considering the most restrictive policies first:

  • Check policies
  • Accept policies

For example, if you have an access rule allowing the user alice@example.com to access a resource with an accept rule, and a rule allowing group:devops which alice@example.com belongs to, to access a resource with a check rule, then the check rule applies.

New tailnets or existing tailnets that have not modified their ACLs have a default SSH policy allowing users to access their own devices using check mode.

The only kinds of connections that are allowed are:

  • From a user to their own devices, as any user including root.
  • From a user to a tagged device, as any user including root.
  • From a tagged device to another tagged device, for any tags. Note that an SSH access rule from a tagged device cannot be in check mode.

Tailscale SSH cannot be used to access nodes that are shared with you.

That is, the broadest policy allowed would be:

{
  "acls": [
    {
      "action": "accept",
      "src": ["*"],
      "dst": ["*:*"]
    }
  ],
  "ssh": [
    {
      "action": "accept",
      "src": ["autogroup:members"],
      "dst": ["autogroup:self"],
      "users": ["root", "autogroup:nonroot"]
    },
    {
      "action": "accept",
      "src": ["autogroup:members"],
      "dst": ["tag:prod"],
      "users": ["root", "autogroup:nonroot"]
    },
    {
      "action": "accept",
      "src": ["tag:logging"],
      "dst": ["tag:prod"],
      "users": ["root", "autogroup:nonroot"]
    },
  ]
}

To allow a user to only SSH to their own devices, as non-root:

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

To allow group:sre to access devices in the production environment tagged tag:prod:


{
  "groups": {
    "group:sre": ["alice@example.com", "bob@example.com"]
  },
  "acls": [
    {
      "action": "accept",
      "src": ["group:sre"],
      "dst": ["tag:prod:*"]
    },
  ],
  "ssh": [
    {
      "action": "accept",
      "src": ["group:sre"],
      "dst": ["tag:prod"],
      "users": ["ubuntu", "root"],
    },
  ]
  "tagOwners": {
    // users in group:sre can apply the tag tag:prod
    "tag:prod": ["group:sre"]
  }
}

Node attributes

Node attributes is currently in alpha.

The nodeAttrs section of the tailnet policy file defines additional attributes that apply to certain devices in your tailnet. You can use this to set different NextDNS configurations for different devices in your tailnet.

A nodeAttrs definition looks like this:

"nodeAttrs": [
    {
        "target": ["my-kid@my-home.com", "tag:server"],
        "attr": [
            "nextdns:abc123",
            "nextdns:no-device-info",
        ],
    },
],

The target field specifies which nodes the attributes apply to. This can be a tag (tag:server), user (alice@example.com), group (group:kids), or *.

The attr field specifies which attributes apply to those nodes:

  • nextdns:abc123, for a NextDNS configuration abc123. If this is used, the attribute overrides the global NextDNS configuration.
  • nextdns:no-device-info, to disable sending device metadata to NextDNS.

Network policy options

In addition to access rules, the tailnet policy file includes a few network-wide policy settings for specialized purposes. Most networks should never need to specify these.

The derpMap section lets you add custom DERP servers to your network, which your devices will use as needed to relay traffic. You can also use this section to disable the use of Tailscale-provided DERP servers, for example to meet corporate compliance requirements. The derpMap section is detailed in the article on running custom DERP servers.

The disableIPv4 field, if set to true, stops assigning Tailscale IPv4 addresses to your devices. All devices in your network will receive exclusively IPv6 Tailscale addresses, and devices that do not support IPv6 (e.g. systems that have IPv6 disabled in the operating system) will be unreachable. This option is intended for users who have a pre-existing conflicting use of the 100.64.0.0/10 carrier-grade NAT address range.

The OneCGNATRoute field controls the routes that Tailscale clients will generate.

Tailscale clients can have either:

  • One large 100.64/10 route to avoid churn in the routing table as nodes go online and offline. (The churn is disruptive to Chromium-based browsers on macOS.)
  • Fine-grained /32 routes.

The possible values for OneCGNATRoute are:

  • An empty string or not provided: Use default heuristics for each platform.
    • On macOS (for Tailscale v1.28 or later), Tailscale will prefer to add one large 100.64/10 route. Tailscale will not do this if there are other interfaces that also route IP addresses in that range.
    • On other platforms, Tailscale will add fine-grained /32 routes for each node.
  • "mac-always": macOS clients will always add one 100.64/10 route.
  • "mac-never": macOS clients will always add fine-grained /32 routes.

The randomizeClientPort field, if set to true, makes devices prefer a random port for WireGuard traffic over the default static port 41641. This option is intended as a workaround for some buggy firewall devices, and should only be enabled after consulting with Tailscale (contact support).

Subnet routers and exit nodes

ACLs can only allow connections, not reject them. ACLs don’t limit discovery of routes. So, you can restrict access to a node separately from access to a subnet that node routes to, if it’s a subnet router; or public IP, if it’s an exit node.

To restrict access to a subnet, ensure that no ACL allows access to those routes. You can enforce this with a test, which will fail if any rule accidentally allows access, like so:

"tests": [
   {
     "src": "not-allowed@example.com",
     "accept": ["100.101.102.103:22"], // allow access to the tailscale IP
     "deny": ["192.168.0.7:22"], // does not allow access to the subnet
   }
],

To restrict access to all exit nodes, only grant access to autogroup:internet to those who you wish to use exit nodes. You can enforce this with a test, which will fail if any rule accidentally allows access to a public IP, like so:

"tests": [
   {
     "src": "not-allowed@example.com",
     "accept": ["100.101.102.103:22"], // allow access to the tailscale IP
     "deny": ["8.8.8.8:22"], // does not allow access to a public IP
   }
],

Right now, there is no way to restrict use of specific exit nodes using ACLs. Subscribe to this GitHub issue for updates.

Taildrop precedence

Taildrop permits you to share files between devices that you are logged in to, even if ACLs are used to restrict access to the devices.

Last updated

WireGuard is a registered
trademark of Jason A. Donenfeld.

© 2023 Tailscale Inc.

Privacy & Terms