Skip to content

Policy

Headscale implements a large portion of Tailscale's policy features, most notably access control based on ACLs and Grants or Tailscale SSH. See limitations to learn about missing features and notable implementation differences between Headscale and Tailscale.

Headscale uses the same huJSON based file format as Tailscale. By default, no policy is loaded which means that Headscale allows all traffic between nodes. To start using a policy file1, specify its path in the policy.path key in the configuration file.

Headscale needs to be reloaded to pick up changes to the policy file. Either reload Headscale via its systemd service (sudo systemctl reload headscale) or by sending a SIGHUP signal (sudo kill -HUP $(pidof headscale)) to the main process. Headscale logs the result of policy processing after each reload.

Please have a look at Tailscale's policy related documentation to learn more:

Getting started

Headscale supports both ACLs and Grants to write an access control policy. We recommend the use of Grants since ACLs are considered legacy and will not receive new features by Tailscale.

Allow All

If you define a policy file but completely omit the "acls" or "grants" section, Headscale will default to an allow all policy. This means all devices connected to your tailnet will be able to communicate freely with each other.

policy.json
{}

Deny All

To prevent all communication within your tailnet, you can include an empty array for the "grants" section in your policy file.

policy.json
{
  "grants": []
}

More examples


Limitations

Autogroups

Headscale supports several Autogroups that automatically include users, destinations, or devices with specific properties. Autogroups provide a convenient way to write policy rules without manually listing individual users or devices.

autogroup:internet

Allows access to the internet through exit nodes. Can only be used in policy destinations.

policy.json
{
  "grants": [
    {
      "src": ["alice@"],
      "dst": ["autogroup:internet"],
      "ip": ["*"]
    }
  ]
}

autogroup:member

Includes all personal (untagged) devices.

policy.json
{
  "grants": [
    {
      "src": ["autogroup:member"],
      "dst": ["tag:prod-app-servers"],
      "ip": ["80,443"]
    }
  ]
}

autogroup:tagged

Includes all devices that have at least one tag.

policy.json
{
  "grants": [
    {
      "src": ["autogroup:tagged"],
      "dst": ["tag:monitoring"],
      "ip": ["9090"]
    }
  ]
}

autogroup:self

Includes devices where the same user is authenticated on both the source and destination. Does not include tagged devices. Can only be used in policy destinations.

policy.json
{
  "grants": [
    {
      "src": ["autogroup:member"],
      "dst": ["autogroup:self"],
      "ip": ["*"]
    }
  ]
}

The current implementation of autogroup:self is inefficient

Using autogroup:self may cause performance degradation on the Headscale coordinator server in large deployments, as filter rules must be compiled per-node rather than globally and the current implementation is not very efficient.

If you experience performance issues, consider using more specific policy rules or limiting the use of autogroup:self.

policy.json
{
  "grants": [
    // The following rules allow internal users to communicate with their
    // own nodes in case autogroup:self is causing performance issues.
    {
      "src": ["boss@"],
      "dst": ["boss@"],
      "ip": "*"
    },
    {
      "src": ["dev1@"],
      "dst": ["dev1@"],
      "ip": "*"
    },
    {
      "src": ["intern1@"],
      "dst": ["intern1@"],
      "ip": "*"
    }
  ]
}

autogroup:nonroot

Used in Tailscale SSH rules to allow access to any user except root. Can only be used in the users field of SSH rules.

policy.json
{
  "ssh": [
    {
      "action": "accept",
      "src": ["autogroup:member"],
      "dst": ["autogroup:self"],
      "users": ["autogroup:nonroot"]
    }
  ]
}

autogroup:danger-all

This autogroup resolves to all IP addresses (0.0.0.0/0 and ::/0) which also includes all IP addresses outside the standard Tailscale IP ranges. This autogroup can only be used as source.

Node Attributes

Node attributes allow for device-specific configuration and attributes. At least the following node attributes are currently supported by Headscale2:

policy.json
{
  "nodeAttrs": [
    {
      // Enable MagicDNS AAAA records for all nodes
      "target": ["*"]
      "attr": ["magicdns-aaaa"]
    }
  ]
}

Network-wide policy options

The following options are applied for the entire tailnet. Consider node attributes for a more fine-grained configuration instead.

policy.json
{
  // Use a random WireGuard port for the entire tailnet
  "randomizeClientPort": true
}

  1. Headscale also allows to store the policy in the database. This is typically only required in case a web interface is used. 

  2. Other key-only node attributes can be used as well. Find them in the client source code with grep -E '^\s+NodeAttr\w+' tailcfg/tailcfg.go or by using GitHub code search (requires login)