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:

- Tailscale policy file: A description of supported sections within the policy file along with links to syntax references for each section.
- ACLs: How to configure access control using ACLs.
- Grants: Introduction to Grants with links to syntax reference, examples and a migration guide from ACLs to Grants.

## 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

- See our documentation on subnet routers and exit nodes to learn how to restrict their use or how to automatically approve them.
- The Tailscale documentation provides a large collection of configuration examples:
  - ACL examples
  - Grants examples
  - SSH configuration
  - Define a tag

## Limitations

- Device postures and the related sections such as `postures` or `srcPosture` aren't supported.
- IP sets aren't supported.
- A subset of Autogroups are available.

## 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:

- `drive:access`, `drive:share`: Taildrive support.
- `nextdns:<profile>`, `nextdns:no-device-info`: NextDNS integration. Be sure to set NextDNS as global resolver in the configuration.
- `magicdns-aaaa`: Respond to AAAA queries on the local MagicDNS resolver at 100.100.100.100.
- `disable-ipv4`: Selectively disable IPv4 for specfic nodes. This is may be useful to workaround CGNat conflicts.
- `randomize-client-port`: Allocate a random port for WireGuard traffic instead of the static default port 41641.
- `disable-captive-portal-detection`: Disable automatic captive portal detection.

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.

- `randomizeClientPort`: Allocate a random port for WireGuard traffic instead of the static default port 41641.

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). ↩