# Technical Documentation: juanfont/headscale

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/juanfont/headscale)

### Overview (README.md)

An open source, self-hosted implementation of the Tailscale control server.

Join our [Discord server](https://discord.gg/c84AZQhmpx) for a chat.

**Note:** Always select the same GitHub tag as the released version you use
to ensure you have the correct example configuration. The `main` branch might
contain unreleased changes. The documentation is available for stable and
development versions:

- [Documentation for the stable version](https://headscale.net/stable/)
- [Documentation for the development version](https://headscale.net/development/)

## What is Tailscale

Tailscale is [a modern VPN](https://tailscale.com/) built on top of
[Wireguard](https://www.wireguard.com/).
It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/)
between the computers of your networks - using
[NAT traversal](https://tailscale.com/blog/how-nat-traversal-works/).

Everything in Tailscale is Open Source, except the GUI clients for proprietary OS
(Windows and macOS/iOS), and the control server.

The control server works as an exchange point of Wireguard public keys for the
nodes in the Tailscale network. It assigns the IP addresses of the clients,
creates the boundaries between each user, enables sharing machines between users,
and exposes the advertised routes of your nodes.

A [Tailscale network (tailnet)](https://tailscale.com/docs/concepts/tailnet) is
private network which Tailscale assigns to a user in terms of private users or an
organisation.

## Design goal

Headscale aims to implement a self-hosted, open source alternative to the
[Tailscale](https://tailscale.com/) control server. Headscale's goal is to
provide self-hosters and hobbyists with an open-source server they can use for
their projects and labs. It implements a narrow scope, a _single_ Tailscale
network (tailnet), suitable for a personal use, or a small open-source
organisation.

## Supporting Headscale

If you like `headscale` and find it useful, there is a sponsorship and donation
buttons available in the repo.

## Features

Please see ["Features" in the documentation](https://headscale.net/stable/about/features/).

## Client OS support

Please see ["Client and operating system support" in the documentation](https://headscale.net/stable/about/clients/).

## Running headscale

**Please note that we do not support nor encourage the use of reverse proxies
and container to run Headscale.**

Please have a look at the [`documentation`](https://headscale.net/stable/).

For NixOS users, a module is available in [`nix/`](./nix/).

## Builds from `main`

Development builds from the `main` branch are available as container images and
binaries. See the [development builds](https://headscale.net/stable/setup/install/main/)
documentation for details.

## Talks

- Fosdem 2026 (video): [Headscale & Tailscale: The complementary open source clone](https://fosdem.org/2026/schedule/event/KYQ3LL-headscale-the-complementary-open-source-clone/)
  - presented by Kristoffer Dalby
- Fosdem 2023 (video): [Headscale: How we are using integration testing to reimplement Tailscale](https://fosdem.org/2023/schedule/event/goheadscale/)
  - presented by Juan Font Alonso and Kristoffer Dalby

## Disclaimer

This project is not associated with Tailscale Inc.

However, one of the active maintainers for Headscale [is employed by Tailscale](https://tailscale.com/blog/opensource) and he is allowed to spend work hours contributing to the project. Contributions from this maintainer are reviewed by other maintainers.

The maintainers work together on setting the direction for the project. The underlying principle is to serve the community of self-hosters, enthusiasts and hobbyists - while having a sustainable project.

## Contributing

Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) file.

Have also a look at our [AI_POLICY.md](./AI_POLICY.md).

### Requirements

To contribute to headscale you would need the latest version of [Go](https://golang.org)
and [Buf](https://buf.build) (Protobuf generator).

We recommend using [Nix](https://nixos.org/) to setup a development environment. This can
be done with `nix develop`, which will install the tools and give you a shell.
This guarantees that you will have the same dev env as `headscale` maintainers.

### Code style

To ensure we have some consistency with a growing number of contributions,
this project has adopted linting and style/formatting rules:

The **Go** code is linted with [`golangci-lint`](https://golangci-lint.run) and
formatted with [`golines`](https://github.com/segmentio/golines) (width 88) and
[`gofumpt`](https://github.com/mvdan/gofumpt).
Please configure your editor to run the tools while developing and make sure to
run `make lint` and `make fmt` before committing any code.

The **Proto** code is linted with [`buf`](https://docs.buf.build/lint/overview) and
formatted with [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html).

The **docs** are formatted with [`mdformat`](https://mdformat.readthedocs.io).

The **rest** (Markdown, YAML, etc) is formatted with [`prettier`](https://prettier.io).

Check out the `.golangci.yaml` and `Makefile` to see the specific configuration.

### Install development tools

- Go
- Buf
- Protobuf tools

Install and activate:

```shell
nix develop
```

### Testing and building

Some parts of the project require the generation of Go code from Protobuf
(if changes are made in `proto/`) and it must be (re-)generated with:

```shell
make generate
```

**Note**: Please check in changes from `gen/` in a separate commit to make it easier to review.

To run the tests:

```shell
make test
```

To build the program:

```shell
make build
```

### Development workflow

We recommend using Nix for dependency management to ensure you have all required tools. If you prefer to manage dependencies yourself, you can use Make directly:

**With Nix (recommended):**

```shell
nix develop
make test
make build
```

**With your own dependencies:**

```shell
make test
make build
```

The Makefile will warn you if any required tools are missing and suggest running `nix develop`. Run `make help` to see all available targets.

## Contributors

<a href="https://github.com/juanfont/headscale/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=juanfont/headscale" />
</a>

Made with [contrib.rocks](https://contrib.rocks).

---

### Index (`docs/index.md`)

---
hide:
  - navigation
  - toc
---

# Welcome to headscale

Headscale is an open source, self-hosted implementation of the Tailscale control server.

This page contains the documentation for the latest version of headscale. Please also check our [FAQ](about/faq.md).

Join our [Discord server](https://discord.gg/c84AZQhmpx) for a chat and community support.

## Design goal

Headscale aims to implement a self-hosted, open source alternative to the
[Tailscale](https://tailscale.com/) control server. Headscale's goal is to
provide self-hosters and hobbyists with an open-source server they can use for
their projects and labs. It implements a narrow scope, a _single_ Tailscale
network (tailnet), suitable for a personal use, or a small open-source
organisation.

## Supporting headscale

Please see [Sponsor](about/sponsor.md) for more information.

## Contributing

Headscale is "Open Source, acknowledged contribution", this means that any
contribution will have to be discussed with the Maintainers before being submitted.

Please see [Contributing](about/contributing.md) for more information.

## About

Headscale is maintained by [Kristoffer Dalby](https://kradalby.no/) and [Juan Font](https://font.eu).

---

### Usage Getting Started (`docs/usage/getting-started.md`)

# Getting started

This page helps you get started with headscale and provides a few usage examples for the headscale command line tool
`headscale`.

!!! note "Prerequisites"

    - Headscale is installed and running as system service. Read the [setup section](../setup/requirements.md) for
      installation instructions.
    - The configuration file exists and is adjusted to suit your environment, see
      [Configuration](../ref/configuration.md) for details.
    - Headscale is reachable from the Internet. Verify this by visiting the health endpoint:
      https://headscale.example.com/health
    - The Tailscale client is installed, see [Client and operating system support](../about/clients.md) for more
      information.

## Getting help

The `headscale` command line tool provides built-in help. To show available commands along with their arguments and
options, run:

=== "Native"

    ```shell
    # Show help
    headscale help

    # Show help for a specific command
    headscale <COMMAND> --help
    ```

=== "Container"

    ```shell
    # Show help
    docker exec -it headscale \
      headscale help

    # Show help for a specific command
    docker exec -it headscale \
      headscale <COMMAND> --help
    ```

!!! note "Manage headscale from another local user"

    By default only the user `headscale` or `root` will have the necessary permissions to access the unix socket
    (`/var/run/headscale/headscale.sock`) that is used to communicate with the service. In order to be able to
    communicate with the headscale service you have to make sure the unix socket is accessible by the user that runs
    the commands. In general you can achieve this by any of the following methods:

    - using `sudo`
    - run the commands as user `headscale`
    - add your user to the `headscale` group

    To verify you can run the following command using your preferred method:

    ```shell
    headscale users list
    ```

## Manage headscale users

In headscale, a node (also known as machine or device) is [typically assigned to a headscale
user](../ref/registration.md#identity-model). Such a headscale user[^1] may have many nodes assigned to them and can be
managed with the `headscale users` command. Invoke the built-in help for more information: `headscale users --help`.

### Create a headscale user

=== "Native"

    ```shell
    headscale users create <USER>
    ```

=== "Container"

    ```shell
    docker exec -it headscale \
      headscale users create <USER>
    ```

### List existing headscale users

=== "Native"

    ```shell
    headscale users list
    ```

=== "Container"

    ```shell
    docker exec -it headscale \
      headscale users list
    ```

## Register a node

One has to [register a node](../ref/registration.md) first to use headscale as coordination server with Tailscale. The
following examples work for the Tailscale client on Linux/BSD operating systems. Alternatively, follow the instructions
to connect [Android](connect/android.md), [Apple](connect/apple.md) or [Windows](connect/windows.md) devices. Read
[registration methods](../ref/registration.md) for an overview of available registration methods.

### [Web authentication](../ref/registration.md#web-authentication)

On a client machine, run the `tailscale up` command and provide the FQDN of your headscale instance as argument:

```shell
tailscale up --login-server <YOUR_HEADSCALE_URL>
```

Usually, a browser window with further instructions is opened. This page explains how to complete the registration on
your headscale server and it also prints the Auth ID required to approve the node:

=== "Native"

    ```shell
    headscale auth register --user <USER> --auth-id <AUTH_ID>
    ```

=== "Container"

    ```shell
    docker exec -it headscale \
      headscale auth register --user <USER> --auth-id <AUTH_ID>
    ```

### [Pre authenticated key](../ref/registration.md#pre-authenticated-key)

It is also possible to generate a preauthkey and register a node non-interactively. First, generate a preauthkey on the
headscale instance. By default, the key is valid for one hour and can only be used once (see `headscale preauthkeys --help` for other options):

=== "Native"

    ```shell
    headscale preauthkeys create --user <USER>
    ```

=== "Container"

    ```shell
    docker exec -it headscale \
      headscale preauthkeys create --user <USER>
    ```

The command returns the preauthkey on success which is used to connect a node to the headscale instance via the
`tailscale up` command:

```shell
tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```

[^1]: [Ensure that the Headscale username does not end with `@`.](../ref/oidc.md#reference-a-user-in-the-policy)

---

### Requirements (`docs/requirements.txt`)

mike~=2.2
mkdocs-include-markdown-plugin~=7.3
mkdocs-macros-plugin~=1.5
mkdocs-materialx[imaging]~=10.2
mkdocs-minify-plugin~=0.8
mkdocs-redirects~=1.2

---

### About Clients (`docs/about/clients.md`)

# Client and operating system support

We aim to support the [**last 10 releases** of the Tailscale client](https://tailscale.com/changelog#client) on all
provided operating systems and platforms. Some platforms might require additional configuration to connect with
headscale.

| OS      | Supports headscale                                                                                    |
| ------- | ----------------------------------------------------------------------------------------------------- |
| Linux   | Yes                                                                                                   |
| OpenBSD | Yes                                                                                                   |
| FreeBSD | Yes                                                                                                   |
| Windows | Yes (see [docs](../usage/connect/windows.md) and `/windows` on your headscale for more information)   |
| Android | Yes (see [docs](../usage/connect/android.md) for more information)                                    |
| macOS   | Yes (see [docs](../usage/connect/apple.md#macos) and `/apple` on your headscale for more information) |
| iOS     | Yes (see [docs](../usage/connect/apple.md#ios) and `/apple` on your headscale for more information)   |
| tvOS    | Yes (see [docs](../usage/connect/apple.md#tvos) and `/apple` on your headscale for more information)  |

---

### About Faq (`docs/about/faq.md`)

# Frequently Asked Questions

## What is the design goal of headscale?

Headscale aims to implement a self-hosted, open source alternative to the
[Tailscale](https://tailscale.com/) control server. Headscale's goal is to
provide self-hosters and hobbyists with an open-source server they can use for
their projects and labs. It implements a narrow scope, a _single_ Tailscale
network (tailnet), suitable for a personal use, or a small open-source
organisation.

## How can I contribute?

Headscale is "Open Source, acknowledged contribution", this means that any
contribution will have to be discussed with the Maintainers before being submitted.

Please see [Contributing](contributing.md) for more information.

## Why is 'acknowledged contribution' the chosen model?

Both maintainers have full-time jobs and families, and we want to avoid burnout. We also want to avoid frustration from contributors when their PRs are not accepted.

We are more than happy to exchange emails, or to have dedicated calls before a PR is submitted.

## When/Why is Feature X going to be implemented?

We use [GitHub Milestones to plan for upcoming Headscale releases](https://github.com/juanfont/headscale/milestones).
Have a look at [our current plan](https://github.com/juanfont/headscale/milestones) to get an idea when a specific
feature is about to be implemented. The release plan is subject to change at any time.

If you're interested in contributing, please post a feature request about it. Please be aware that there are a number of
reasons why we might not accept specific contributions:

- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
- Given that we are reverse-engineering Tailscale to satisfy our own curiosity, we might be interested in implementing the feature ourselves.
- You are not sending unit and integration tests with it.

## Do you support Y method of deploying headscale?

We currently support deploying headscale using our binaries and the DEB packages. Visit our [installation guide using
official releases](../setup/install/official.md) for more information.

In addition to that, you may use packages provided by the community or from distributions. Learn more in the
[installation guide using community packages](../setup/install/community.md).

For convenience, we also [build container images with headscale](../setup/install/container.md). But **please be aware that
we don't officially support deploying headscale using Docker**. On our [Discord server](https://discord.gg/c84AZQhmpx)
we have a "docker-issues" channel where you can ask for Docker-specific help to the community.

## What is the recommended update path? Can I skip multiple versions while updating?

Please follow the steps outlined in the [upgrade guide](../setup/upgrade.md) to update your existing Headscale
installation. Its required to update from one stable version to the next (e.g. 0.26.0 → 0.27.1 → 0.28.0) without
skipping minor versions in between. You should always pick the latest available patch release.

Be sure to check the [changelog](https://github.com/juanfont/headscale/blob/main/CHANGELOG.md) for version specific
upgrade instructions and breaking changes.

## Scaling / How many clients does Headscale support?

It depends. As often stated, Headscale is not enterprise software and our focus
is homelabbers and self-hosters. Of course, we do not prevent people from using
it in a commercial/professional setting and often get questions about scaling.

Please note that when Headscale is developed, performance is not part of the
consideration as the main audience is considered to be users with a modest
amount of devices. We focus on correctness and feature parity with Tailscale
SaaS over time.

To understand if you might be able to use Headscale for your use case, I will
describe two scenarios in an effort to explain what is the central bottleneck
of Headscale:

1. An environment with 1000 servers

    - they rarely "move" (change their endpoints)
    - new nodes are added rarely

1. An environment with 80 laptops/phones (end user devices)

    - nodes move often, e.g. switching from home to office

Headscale calculates a map of all nodes that need to talk to each other,
creating this "world map" requires a lot of CPU time. When an event that
requires changes to this map happens, the whole "world" is recalculated, and a
new "world map" is created for every node in the network.

This means that under certain conditions, Headscale can likely handle 100s
of devices (maybe more), if there is _little to no change_ happening in the
network. For example, in Scenario 1, the process of computing the world map is
extremely demanding due to the size of the network, but when the map has been
created and the nodes are not changing, the Headscale instance will likely
return to a very low resource usage until the next time there is an event
requiring the new map.

In the case of Scenario 2, the process of computing the world map is less
demanding due to the smaller size of the network, however, the type of nodes
will likely change frequently, which would lead to a constant resource usage.

Headscale will start to struggle when the two scenarios overlap, e.g. many nodes
with frequent changes will cause the resource usage to remain constantly high.
In the worst case scenario, the queue of nodes waiting for their map will grow
to a point where Headscale never will be able to catch up, and nodes will never
learn about the current state of the world.

We expect that the performance will improve over time as we improve the code
base, but it is not a focus. In general, we will never make the tradeoff to make
things faster on the cost of less maintainable or readable code. We are a small
team and have to optimise for maintainability.

## Which database should I use?

We recommend the use of SQLite as database for headscale:

- SQLite is simple to setup and easy to use
- It scales well for all of headscale's use cases
- Development and testing happens primarily on SQLite
- PostgreSQL is still supported, but is considered to be in "maintenance mode"

The headscale project itself does not provide a tool to migrate from PostgreSQL to SQLite. Please have a look at [the
related tools documentation](../ref/integration/tools.md) for migration tooling provided by the community.

The choice of database has little to no impact on the performance of the server,
see [Scaling / How many clients does Headscale support?](#scaling-how-many-clients-does-headscale-support) for understanding how Headscale spends its resources.

## Why is my reverse proxy not working with headscale?

We don't know. We don't use reverse proxies with headscale ourselves, so we don't have any experience with them. We have
[community documentation](../ref/integration/reverse-proxy.md) on how to configure various reverse proxies, and a
dedicated "reverse-proxy-issues" channel on our [Discord server](https://discord.gg/c84AZQhmpx) where you can ask for
help to the community.

## Can I use headscale and tailscale on the same machine?

Running headscale on a machine that is also in the tailnet can cause problems with subnet routers, traffic relay nodes, and MagicDNS. It might work, but it is not supported.

## Why do two nodes see each other in their status, even if a policy rule allows traffic only in one direction?

A frequent use case is to allow traffic only from one node to another, but not the other way around. For example, the
workstation of an administrator should be able to connect to all nodes but the nodes themselves shouldn't be able to
connect back to the administrator's node. Why do all nodes see the administrator's workstation in the output of
`tailscale status`?

This is essentially how Tailscale works. If traffic is allowed to flow in one direction, then both nodes see each other
in their output of `tailscale status`. Traffic is still filtered according to the policy, with the exception of
`tailscale ping` which is always allowed in either direction.

See also <https://tailscale.com/docs/concepts/device-visibility>.

## My policy is stored in the database and Headscale refuses to start due to an invalid policy. How can I recover?

Headscale checks if the policy is valid during startup and refuses to start if it detects an error. The error message
indicates which part of the policy is invalid. Follow these steps to fix your policy:

- Dump the policy to a file: `headscale policy get --bypass-server-and-access-database-directly > policy.json`
- Edit and fixup `policy.json`. Use the command `headscale policy check --file policy.json` to validate the policy.
- Load the modified policy: `headscale policy set --bypass-server-and-access-database-directly --file policy.json`
- Start Headscale as usual.

!!! warning "Full server configuration required"

    The above commands to get/set the policy require a complete server configuration file including database settings. A
    minimal config to [control Headscale via remote CLI](../ref/api.md#remote-control) is not sufficient. You may use
    `headscale -c /path/to/config.yaml` to specify the path to an alternative configuration file.

## How can I migrate back to the recommended IP prefixes?

Tailscale only supports the IP prefixes `100.64.0.0/10` and `fd7a:115c:a1e0::/48` or smaller subnets thereof. The
following steps can be used to migrate from unsupported IP prefixes back to the supported and recommended ones.

!!! warning "Backup and test in a demo environment required"

    The commands below update the IP addresses of all nodes in your tailnet and this might have a severe impact in your
    specific environment. At a minimum:

    - [Create a backup of your database](../setup/upgrade.md#backup)
    - Test the commands below in a representive demo environment. This allows to catch subsequent connectivity errors
      early and see how the tailnet behaves in your specific environment.

- Stop Headscale
- Restore the default prefixes in the [configuration file](../ref/configuration.md):
    ```yaml
    prefixes:
      v4: 100.64.0.0/10
      v6: fd7a:115c:a1e0::/48
    ```
- Update the `nodes.ipv4` and `nodes.ipv6` columns in the database and assign each node a unique IPv4 and IPv6 address.
  The following SQL statement assigns IP addresses based on the node ID:
    ```sql
    UPDATE nodes
    SET ipv4=concat('100.64.', id/256, '.', id%256),
        ipv6=concat('fd7a:115c:a1e0::', format('%x', id));
    ```
- Update the [policy](../ref/policy.md) to reflect the IP address changes (if any)
- Start Headscale

Nodes should reconnect within a few seconds and pickup their newly assigned IP addresses.

## How can I avoid to send logs to Tailscale Inc?

A Tailscale client [collects logs about its operation and connection attempts with other
clients](https://tailscale.com/docs/features/logging#client-logs) and sends them to a central log service operated by
Tailscale Inc.

Headscale, by default, instructs clients to disable log submission to the central log service. This configuration is
applied by a client once it successfully connected with Headscale. See the configuration option `logtail.enabled` in the
[configuration file](../ref/configuration.md) for details.

Do not grant the `https://tailscale.com/cap/data-plane-audit-logs` node attribute while `logtail.enabled` is `false`. A
client with log submission disabled treats this attribute as "the tailnet requires logging" and turns itself off, as
`tailscale down` would. It stays off until the attribute is removed and `tailscale up` is run. A client started with
`TS_NO_LOGS_NO_SUPPORT=true` or `--no-logs-no-support` reacts the same way, whatever `logtail.enabled` says.

Alternatively, logging can also be disabled on the client side. This is independent of Headscale and opting out of
client logging disables log submission early during client startup. The configuration is operating system specific and
is usually achieved by:

- setting the environment variable `TS_NO_LOGS_NO_SUPPORT=true` or
- by passing the flag `--no-logs-no-support` to `tailscaled` or
- by disabling "Remote client logging" in the mobile app settings

See <https://tailscale.com/docs/features/logging#opt-out-of-client-logging> for details.

---

### About Features (`docs/about/features.md`)

# Features

Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is
to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. This page
provides on overview of Headscale's feature and compatibility with the Tailscale control server:

- [x] Full "base" support of Tailscale's features
- [x] [Node registration](../ref/registration.md)
    - [x] [Web authentication](../ref/registration.md#web-authentication)
    - [x] [Pre authenticated key](../ref/registration.md#pre-authenticated-key)
- [x] [DNS](../ref/dns.md)
    - [x] [MagicDNS](https://tailscale.com/docs/features/magicdns)
    - [x] [Global and restricted nameservers (split DNS)](https://tailscale.com/docs/reference/dns-in-tailscale#nameservers)
    - [x] [search domains](https://tailscale.com/docs/reference/dns-in-tailscale#search-domains)
    - [x] [Extra DNS records (Headscale only)](../ref/dns.md#setting-extra-dns-records)
- [x] File sharing
    - [x] [Taildrive](https://tailscale.com/docs/features/taildrive)
    - [x] [Taildrop](https://tailscale.com/docs/features/taildrop)
- [x] [Tags](../ref/tags.md)
- [x] [Routes](../ref/routes.md)
    - [x] [Subnet routers](../ref/routes.md#subnet-router)
    - [x] [Exit nodes](../ref/routes.md#exit-node)
    - [x] [Route filtering with Via](https://tailscale.com/docs/features/access-control/grants/grants-via)
- [x] Dual stack (IPv4 and IPv6)
- [x] Ephemeral nodes
- [x] Embedded [DERP server](../ref/derp.md)
- [x] [Peer relays](https://tailscale.com/docs/features/peer-relay)
- [x] [Policy](../ref/policy.md) ([GitHub label "policy"](https://github.com/juanfont/headscale/labels/policy%20%F0%9F%93%9D))
    - [x] ACLs
    - [x] Grants
    - [x] Some [Autogroups](../ref/policy.md#autogroups)
    - [x] [Auto approvers](https://tailscale.com/docs/reference/syntax/policy-file#auto-approvers) for [subnet
      routers](../ref/routes.md#automatically-approve-routes-of-a-subnet-router) and [exit
      nodes](../ref/routes.md#automatically-approve-an-exit-node-with-auto-approvers)
    - [x] [Tailscale SSH](https://tailscale.com/docs/features/tailscale-ssh)
    - [x] [Node attributes](../ref/policy.md#node-attributes)
    - [x] [Tests](https://tailscale.com/docs/reference/syntax/policy-file#tests) and
      [sshTests](https://tailscale.com/docs/reference/syntax/policy-file#ssh-tests)
- [x] [Node registration using Single-Sign-On (OpenID Connect)](../ref/oidc.md) ([GitHub label "OIDC"](https://github.com/juanfont/headscale/labels/OIDC))
    - [x] Basic registration
    - [x] Update user profile from identity provider
    - [ ] OIDC groups cannot be used in ACLs
- [ ] [Funnel](https://tailscale.com/docs/features/tailscale-funnel) ([#1040](https://github.com/juanfont/headscale/issues/1040))
- [ ] [Serve](https://tailscale.com/docs/features/tailscale-serve) ([#1234](https://github.com/juanfont/headscale/issues/1921))
- [ ] [Network flow logs](https://tailscale.com/docs/features/logging/network-flow-logs) ([#1687](https://github.com/juanfont/headscale/issues/1687))

---

### About Help (`docs/about/help.md`)

# Getting help

Join our [Discord server](https://discord.gg/c84AZQhmpx) for announcements and community support.

Please report bugs via [GitHub issues](https://github.com/juanfont/headscale/issues)

---

### About Releases (`docs/about/releases.md`)

# Releases

All headscale releases are available on the [GitHub release page](https://github.com/juanfont/headscale/releases). Those
releases are available as binaries for various platforms and architectures, packages for Debian based systems and source
code archives. Container images are available on [Docker Hub](https://hub.docker.com/r/headscale/headscale) and
[GitHub Container Registry](https://github.com/juanfont/headscale/pkgs/container/headscale).

An Atom/RSS feed of headscale releases is available [here](https://github.com/juanfont/headscale/releases.atom).

See the "announcements" channel on our [Discord server](https://discord.gg/c84AZQhmpx) for news about headscale.

---

### About Sponsor (`docs/about/sponsor.md`)

# Sponsor

If you like to support the development of headscale, please consider a donation via
[ko-fi.com/headscale](https://ko-fi.com/headscale). Thank you!

---

### Ref Api (`docs/ref/api.md`)

# API

Headscale provides a [HTTP REST API](#rest-api) which may be used to integrate a [web
interface](integration/web-ui.md), [remote control Headscale](#remote-control) or provide a base for custom
integration and tooling.

The API requires a valid API key before use. To create an API key, log into your Headscale server and generate
one with the default expiration of 90 days:

```shell
headscale apikeys create
```

Copy the output of the command and save it for later. Please note that you can not retrieve an API key again. If the API
key is lost, expire the old one, and create a new one.

To list the API keys currently associated with the server:

```shell
headscale apikeys list
```

and to expire an API key:

```shell
headscale apikeys expire --prefix <PREFIX>
```

## REST API

- API endpoint: `/api/v1`, e.g. `https://headscale.example.com/api/v1`
- Documentation: `/api/v1/docs`, e.g. `https://headscale.example.com/api/v1/docs`
- Headscale Version: `/version`, e.g. `https://headscale.example.com/version`
- Authenticate using HTTP Bearer authentication by sending the [API key](#api) with the HTTP `Authorization: Bearer <API_KEY>` header.

Start by [creating an API key](#api) and test it with the examples below. Read the API documentation provided by your
Headscale server at `/api/v1/docs` for details.

=== "Get details for all users"

    ```console
    curl -H "Authorization: Bearer <API_KEY>" \
        https://headscale.example.com/api/v1/user
    ```

=== "Get details for user 'bob'"

    ```console
    curl -H "Authorization: Bearer <API_KEY>" \
        https://headscale.example.com/api/v1/user?name=bob
    ```

=== "Register a node"

    ```console
    curl -H "Authorization: Bearer <API_KEY>" \
        --json '{"user": "<USER>", "authId": "<AUTH_ID>"}' \
        https://headscale.example.com/api/v1/auth/register
    ```

## Join nodes with an OAuth client

Headscale also serves a subset of the Tailscale-compatible API at `/api/v2`, which
accepts **OAuth 2.0 client-credentials** in addition to API keys. The Tailscale
client can use an OAuth client secret in place of an auth key: it exchanges the
secret for an access token, mints a single-use tagged auth key and registers with
it. This works anywhere the client takes an auth key: `tailscale up`, the
container image, `tsnet` and the
[`tailscale/github-action`](https://github.com/tailscale/github-action). One
long-lived secret joins any number of nodes.

Create an OAuth client with the `auth_keys` scope and the tags its nodes get. The
secret is shown once:

```shell
headscale oauth-clients create --scope auth_keys --tag tag:ci
```

Build the auth key from the secret:

1. Swap the prefix: `hskey-client-…` becomes `tskey-client-…`. The client only
   runs the exchange for `tskey-client-`; Headscale accepts both.
1. Append `?baseURL=<your Headscale URL>`.
1. Set `--advertise-tags`. Each tag must exist in the policy's `tagOwners` and be
   one of the OAuth client's tags, or owned by one of them.

```text
tskey-client-<id>-<secret>?baseURL=https://headscale.example.com
```

!!! warning

    Without `baseURL` the client sends the secret to `https://api.tailscale.com`.

### Attributes

These are all the attributes the client understands; any other is an error.
Order does not matter and an empty value means the default.

| Attribute       | Default                     | Effect                                                                                           |
| --------------- | --------------------------- | ------------------------------------------------------------------------------------------------ |
| `baseURL`       | `https://api.tailscale.com` | Where the exchange and key creation go. Your Headscale URL, no trailing slash.                   |
| `ephemeral`     | `true`                      | Node is removed after it goes offline (`node.ephemeral.inactivity_timeout`). `false` to keep it. |
| `preauthorized` | `false`                     | Accepted, no effect: Headscale always authorizes pre-auth-key nodes.                             |

Booleans take any Go `strconv.ParseBool` value (`true`, `false`, `1`, `0`, …).

### Examples

`tailscale up`, either as the auth key or via `--client-secret` (which also takes
`file:/path/to/secret`):

```shell
tailscale up --login-server https://headscale.example.com --advertise-tags tag:ci \
  --auth-key 'tskey-client-<id>-<secret>?baseURL=https://headscale.example.com&ephemeral=false'
```

Container image:

```shell
docker run -d --name tailscale \
  -e TS_AUTHKEY='tskey-client-<id>-<secret>?baseURL=https://headscale.example.com' \
  -e TS_EXTRA_ARGS='--login-server=https://headscale.example.com --advertise-tags=tag:ci' \
  tailscale/tailscale
```

`tsnet` (`TS_CLIENT_SECRET` works too); import `tailscale.com/feature/oauthkey`:

```go
srv := &tsnet.Server{
    ControlURL:    "https://headscale.example.com",
    AuthKey:       "tskey-client-<id>-<secret>?baseURL=https://headscale.example.com",
    AdvertiseTags: []string{"tag:ci"},
}
```

GitHub Action, with the whole string stored as a repository secret:

{% raw %}

```yaml
- uses: tailscale/github-action@v4
  with:
    authkey: ${{ secrets.HEADSCALE_AUTHKEY }}
    args: --login-server=https://headscale.example.com --advertise-tags=tag:ci
```

{% endraw %}

Use `authkey` even though upstream marks it deprecated: `oauth-secret` appends its
own `?…` to the secret, which corrupts `baseURL`.

## Remote control

The `headscale` binary can control a Headscale instance from a remote machine over the HTTP API.

### Prerequisite

- A workstation to run `headscale` (any supported platform, e.g. Linux).
- The Headscale server reachable over HTTP(S).
- An [API key](#api) to authenticate with the Headscale server.

### Setup remote control

1. Download the [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases). Make
   sure to use the same version as on the server.

1. Put the binary somewhere in your `PATH`, e.g. `/usr/local/bin/headscale`

1. Make `headscale` executable: `chmod +x /usr/local/bin/headscale`

1. [Create an API key](#api) on the Headscale server.

1. Provide the connection parameters for the remote Headscale server either via a minimal YAML configuration file or
   via environment variables:

    === "Minimal YAML configuration file"

        ```yaml title="config.yaml"
        cli:
            address: <HEADSCALE_URL>
            api_key: <API_KEY>
        ```

    === "Environment variables"

        ```shell
        export HEADSCALE_CLI_ADDRESS="<HEADSCALE_URL>"
        export HEADSCALE_CLI_API_KEY="<API_KEY>"
        ```

    This instructs the `headscale` binary to connect to a remote instance at `<HEADSCALE_URL>` (e.g.
    `https://headscale.example.com`), instead of connecting to the local instance. A bare host without a scheme is
    assumed to be `https`.

1. Test the connection by listing all nodes:

    ```shell
    headscale nodes list
    ```

    You should now be able to see a list of your nodes from your workstation, and you can
    now control the Headscale server from your workstation.

### Behind a proxy

The remote CLI uses the same HTTP API as everything else, so it works through the reverse proxy already in front of
Headscale with no extra setup.

### Troubleshooting

- Make sure you have the _same_ Headscale version on your server and workstation.
- Verify that your TLS certificate is valid and trusted.
- If you don't have access to a trusted certificate (e.g. from Let's Encrypt), either:
    - Add your self-signed certificate to the trust store of your OS _or_
    - Disable certificate verification by either setting `cli.insecure: true` in the configuration file or by setting
      `HEADSCALE_CLI_INSECURE=1` via an environment variable. We do **not** recommend to disable certificate validation.

---

### Ref Configuration (`docs/ref/configuration.md`)

# Configuration

- Headscale loads its configuration from a YAML file
- It searches for `config.yaml` in the following paths:
    - `/etc/headscale`
    - `$HOME/.headscale`
    - the current working directory
- To load the configuration from a different path, use:
    - the command line flag `-c`, `--config`
    - the environment variable `HEADSCALE_CONFIG`
- Validate the configuration file with: `headscale configtest`

!!! example "Get the [example configuration from the GitHub repository](https://github.com/juanfont/headscale/blob/main/config-example.yaml)"

    Always select the [same GitHub tag](https://github.com/juanfont/headscale/tags) as the released version you use to
    ensure you have the correct example configuration. The `main` branch might contain unreleased changes.

    === "View on GitHub"

        - Development version: <https://github.com/juanfont/headscale/blob/main/config-example.yaml>
        - Version {{ headscale.version }}: https://github.com/juanfont/headscale/blob/v{{ headscale.version }}/config-example.yaml

    === "Download with `wget`"

        ```shell
        # Development version
        wget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml

        # Version {{ headscale.version }}
        wget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/v{{ headscale.version }}/config-example.yaml
        ```

    === "Download with `curl`"

        ```shell
        # Development version
        curl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml

        # Version {{ headscale.version }}
        curl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/v{{ headscale.version }}/config-example.yaml
        ```

---

### Ref Debug (`docs/ref/debug.md`)

# Debugging and troubleshooting

Headscale and Tailscale provide debug and introspection capabilities that can be helpful when things don't work as
expected. This page explains some debugging techniques to help pinpoint problems.

Please also have a look at [Tailscale's Troubleshooting guide](https://tailscale.com/docs/reference/troubleshooting). It
offers a many tips and suggestions to troubleshoot common issues.

## Tailscale

The Tailscale client itself offers many commands to introspect its state as well as the state of the network:

- [Check local network conditions](https://tailscale.com/docs/reference/tailscale-cli#netcheck): `tailscale netcheck`
- [Get the client status](https://tailscale.com/docs/reference/tailscale-cli#status): `tailscale status --json`
- [Get DNS status](https://tailscale.com/docs/reference/tailscale-cli#dns): `tailscale dns status --all`
- Client logs: `tailscale debug daemon-logs`
- Client netmap: `tailscale debug netmap`
- Test DERP connection: `tailscale debug derp headscale`
- And many more, see: `tailscale debug --help`

Many of the commands are helpful when trying to understand differences between Headscale and Tailscale SaaS.

## Headscale

### Application logging

The log levels `debug` and `trace` can be useful to get more information from Headscale.

```yaml hl_lines="3"
log:
  # Valid log levels: panic, fatal, error, warn, info, debug, trace
  level: debug
```

### Database logging

The database debug mode logs all database queries. Enable it to see how Headscale interacts with its database. This also
requires the application log level to be set to either `debug` or `trace`.

```yaml hl_lines="3 7"
database:
  # Enable debug mode. This setting requires the log.level to be set to "debug" or "trace".
  debug: false

log:
  # Valid log levels: panic, fatal, error, warn, info, debug, trace
  level: debug
```

### Metrics and debug endpoint

Headscale provides a metrics and debug endpoint. It allows to introspect different aspects such as:

- Information about the Go runtime, memory usage and statistics
- Connected nodes and pending registrations
- Active policy, filters and SSH policy
- Current DERPMap
- Prometheus metrics

!!! warning "Keep the metrics and debug endpoint private"

    The listen address and port can be configured with the `metrics_listen_addr` variable in the [configuration
    file](configuration.md). By default it listens on localhost, port 9090.

    Keep the metrics and debug endpoint private to your internal network and don't expose it to the Internet.

    The metrics and debug interface can be disabled completely by setting `metrics_listen_addr: null` in the
    [configuration file](configuration.md).

Query metrics via <http://localhost:9090/metrics> and get an overview of available debug information via
<http://localhost:9090/debug/>. Metrics may be queried from outside localhost but the debug interface is subject to
additional protection despite listening on all interfaces.

=== "Direct access"

    Access the debug interface directly on the server where Headscale is installed.

    ```console
    curl http://localhost:9090/debug/
    ```

=== "SSH port forwarding"

    Use SSH port forwarding to forward Headscale's metrics and debug port to your device.

    ```console
    ssh <HEADSCALE_SERVER> -L 9090:localhost:9090
    ```

    Access the debug interface on your device by opening <http://localhost:9090/debug/> in your web browser.

=== "Via debug key"

    The access control of the debug interface supports the use of a debug key. Traffic is accepted if the path to a
    debug key is set via the environment variable `TS_DEBUG_KEY_PATH` and the debug key sent as value for `debugkey`
    parameter with each request.

    ```console
    openssl rand -hex 32 | tee debugkey.txt
    export TS_DEBUG_KEY_PATH=debugkey.txt
    headscale serve
    ```

    Access the debug interface on your device by opening `http://<IP_OF_HEADSCALE>:9090/debug/?debugkey=<DEBUG_KEY>` in
    your web browser. The `debugkey` parameter must be sent with every request.

=== "Via debug IP address"

    The debug endpoint expects traffic from localhost. A different debug IP address may be configured by setting the
    `TS_ALLOW_DEBUG_IP` environment variable before starting Headscale. The debug IP address is ignored when the HTTP
    header `X-Forwarded-For` is present.

    ```console
    export TS_ALLOW_DEBUG_IP=192.168.0.10       # IP address of your device
    headscale serve
    ```

    Access the debug interface on your device by opening `http://<IP_OF_HEADSCALE>:9090/debug/` in your web browser.

---

### Ref Derp (`docs/ref/derp.md`)

# DERP

A [DERP (Designated Encrypted Relay for Packets) server](https://tailscale.com/docs/reference/derp-servers) is mainly
used to relay traffic between two nodes in case a direct connection can't be established. Headscale provides an embedded
DERP server to ensure seamless connectivity between nodes.

## Configuration

DERP related settings are configured within the `derp` section of the [configuration file](configuration.md). The
following sections only use a few of the available settings, check the [example configuration](configuration.md) for
all available configuration options.

### Enable embedded DERP

Headscale ships with an embedded DERP server which allows to run your own self-hosted DERP server easily. The embedded
DERP server is disabled by default and needs to be enabled. In addition, you should configure the public IPv4 and public
IPv6 address of your Headscale server for improved connection stability:

```yaml title="config.yaml" hl_lines="3-5"
derp:
  server:
    enabled: true
    ipv4: 198.51.100.1
    ipv6: 2001:db8::1
```

Keep in mind that [additional ports are needed to run a DERP server](../setup/requirements.md#ports-in-use). Besides
relaying traffic, it also uses STUN (udp/3478) to help clients discover their public IP addresses and perform NAT
traversal. [Check DERP server connectivity](#check-derp-server-connectivity) to see if everything works.

### Remove Tailscale's DERP servers

Once enabled, Headscale's embedded DERP is added to the list of free-to-use [DERP
servers](https://tailscale.com/docs/reference/derp-servers) offered by Tailscale Inc. To only use Headscale's embedded
DERP server, disable the loading of the default DERP map:

```yaml title="config.yaml" hl_lines="6"
derp:
  server:
    enabled: true
    ipv4: 198.51.100.1
    ipv6: 2001:db8::1
  urls: []
```

!!! warning "Single point of failure"

    Removing Tailscale's DERP servers means that there is now just a single DERP server available for clients. This is a
    single point of failure and could hamper connectivity.

    [Check DERP server connectivity](#check-derp-server-connectivity) with your embedded DERP server before removing
    Tailscale's DERP servers.

### Customize DERP map

The DERP map offered to clients can be customized with a [dedicated YAML-configuration
file](https://github.com/juanfont/headscale/blob/main/derp-example.yaml). The file extension picks the format: `.yaml`
or `.yml`, or the JSON format Tailscale serves DERP maps in as `.json` or `.hujson`. This allows to modify previously
loaded DERP maps fetched via URL or to offer your own, custom DERP servers to nodes.

=== "Remove specific DERP regions"

    The free-to-use [DERP servers](https://tailscale.com/docs/reference/derp-servers) are organized into regions via a
    region ID. You can explicitly disable a specific region by setting its region ID to `null`. The following sample
    `derp.yaml` disables the New York DERP region (which has the region ID 1):

    ```yaml title="derp.yaml"
    regions:
      1: null
    ```

    Use the following configuration to serve the default DERP map (excluding New York) to nodes:

    ```yaml title="config.yaml" hl_lines="6 7"
    derp:
      server:
        enabled: false
      urls:
        - https://controlplane.tailscale.com/derpmap/default
      paths:
        - /etc/headscale/derp.yaml
    ```

=== "Provide custom DERP servers"

    The following sample `derp.yaml` references two custom regions (`custom-east` with ID 900 and `custom-west` with ID 901)
    with one custom DERP server in each region. Each DERP server offers DERP relay via HTTPS on tcp/443, support for captive
    portal checks via HTTP on tcp/80 and STUN on udp/3478. See the definitions of
    [DERPMap](https://pkg.go.dev/tailscale.com/tailcfg#DERPMap),
    [DERPRegion](https://pkg.go.dev/tailscale.com/tailcfg#DERPRegion) and
    [DERPNode](https://pkg.go.dev/tailscale.com/tailcfg#DERPNode) for all available options.

    ```yaml title="derp.yaml"
    regions:
      900:
        regionid: 900
        regioncode: custom-east
        regionname: My region (east)
        nodes:
          - name: 900a
            regionid: 900
            hostname: derp900a.example.com
            ipv4: 198.51.100.1
            ipv6: 2001:db8::1
            canport80: true
      901:
        regionid: 901
        regioncode: custom-west
        regionname: My Region (west)
        nodes:
          - name: 901a
            regionid: 901
            hostname: derp901a.example.com
            ipv4: 198.51.100.2
            ipv6: 2001:db8::2
            canport80: true
    ```

    Use the following configuration to only serve the two DERP servers from the above `derp.yaml`:

    ```yaml title="config.yaml" hl_lines="5 6"
    derp:
      server:
        enabled: false
      urls: []
      paths:
        - /etc/headscale/derp.yaml
    ```

Independent of the custom DERP map, you may choose to [enable the embedded DERP server and have it automatically added
to the custom DERP map](#enable-embedded-derp).

### Verify clients

Access to DERP serves can be restricted to nodes that are members of your Tailnet. Relay access is denied for unknown
clients.

=== "Embedded DERP"

    Client verification is enabled by default.

    ```yaml title="config.yaml" hl_lines="3"
    derp:
      server:
        verify_clients: true
    ```

=== "3rd-party DERP"

    Tailscale's `derper` provides two parameters to configure client verification:

    - Use the `-verify-client-url` parameter of the `derper` and point it towards the `/verify` endpoint of your
      Headscale server (e.g `https://headscale.example.com/verify`). The DERP server will query your Headscale instance
      as soon as a client connects with it to ask whether access should be allowed or denied. Access is allowed if
      Headscale knows about the connecting client and denied otherwise.
    - The parameter `-verify-client-url-fail-open` controls what should happen when the DERP server can't reach the
      Headscale instance. By default, it will allow access if Headscale is unreachable.

## Check DERP server connectivity

Any Tailscale client may be used to introspect the DERP map and to check for connectivity issues with DERP servers.

- Display DERP map: `tailscale debug derp-map`
- Check connectivity with the embedded DERP[^1]:`tailscale debug derp headscale`

Additional DERP related metrics and information is available via the [metrics and debug
endpoint](debug.md#metrics-and-debug-endpoint).

## Limitations

- The embedded DERP server can't be used for Tailscale's captive portal checks as it doesn't support the `/generate_204`
  endpoint via HTTP on port tcp/80.
- There are no speed or throughput optimisations, the main purpose is to assist in node connectivity.

[^1]: This assumes that the default region code of the [configuration file](configuration.md) is used.

---

### Ref Dns (`docs/ref/dns.md`)

# DNS

Headscale supports [most DNS features](../about/features.md) from Tailscale. DNS related settings can be configured
within the `dns` section of the [configuration file](configuration.md).

## Setting extra DNS records

Headscale allows to set extra DNS records which are made available via
[MagicDNS](https://tailscale.com/docs/features/magicdns). Extra DNS records can be configured either via static entries
in the [configuration file](configuration.md) or from a JSON file that Headscale continuously watches for changes:

- Use the `dns.extra_records` option in the [configuration file](configuration.md) for entries that are static and
  don't change while Headscale is running. Those entries are processed when Headscale is starting up and changes to the
  configuration require a restart of Headscale.
- For dynamic DNS records that may be added, updated or removed while Headscale is running or DNS records that are
  generated by scripts the option `dns.extra_records_path` in the [configuration file](configuration.md) is useful.
  Set it to the absolute path of the JSON file containing DNS records and Headscale processes this file as it detects
  changes.

An example use case is to serve multiple apps on the same host via a reverse proxy like NGINX, in this case a Prometheus
monitoring stack. This allows to nicely access the service with "http://grafana.myvpn.example.com" instead of the
hostname and port combination "http://hostname-in-magic-dns.myvpn.example.com:3000".

!!! warning "Limitations"

    Currently, [only A and AAAA records are processed by Tailscale](https://github.com/tailscale/tailscale/blob/v1.86.5/ipn/ipnlocal/node_backend.go#L662).

1. Configure extra DNS records using one of the available configuration options:

    === "Static entries, via `dns.extra_records`"

        ```yaml title="config.yaml"
        dns:
          ...
          extra_records:
            - name: "grafana.myvpn.example.com"
              type: "A"
              value: "100.64.0.3"

            - name: "prometheus.myvpn.example.com"
              type: "A"
              value: "100.64.0.3"
          ...
        ```

        Restart your headscale instance.

    === "Dynamic entries, via `dns.extra_records_path`"

        ```json title="extra-records.json"
        [
          {
            "name": "grafana.myvpn.example.com",
            "type": "A",
            "value": "100.64.0.3"
          },
          {
            "name": "prometheus.myvpn.example.com",
            "type": "A",
            "value": "100.64.0.3"
          }
        ]
        ```

        Headscale picks up changes to the above JSON file automatically.

        !!! tip "Good to know"

            - The `dns.extra_records_path` option in the [configuration file](configuration.md) needs to reference the
              file containing extra DNS records. Its extension picks the format: `.json`, `.hujson`, `.yaml` or `.yml`.
            - Be sure to "sort keys" and produce a stable output in case you generate the JSON file with a script.
              Headscale uses a checksum to detect changes to the file and a stable output avoids unnecessary processing.

1. Verify that DNS records are properly set using the DNS querying tool of your choice:

    === "Query with dig"

        ```console
        dig +short grafana.myvpn.example.com
        100.64.0.3
        ```

    === "Query with drill"

        ```console
        drill -Q grafana.myvpn.example.com
        100.64.0.3
        ```

1. Optional: Setup the reverse proxy

    The motivating example here was to be able to access internal monitoring services on the same host without
    specifying a port, depicted as NGINX configuration snippet:

    ```nginx title="nginx.conf"
    server {
        listen 80;
        listen [::]:80;

        server_name grafana.myvpn.example.com;

        location / {
            proxy_pass http://localhost:3000;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

    }
    ```

---

### Ref Oidc (`docs/ref/oidc.md`)

# OpenID Connect

Headscale supports authentication via external identity providers using OpenID Connect (OIDC). It features:

- Auto configuration via OpenID Connect Discovery Protocol
- [Proof Key for Code Exchange (PKCE) code verification](#enable-pkce-recommended)
- [Authorization based on a user's domain, email address or group membership](#authorize-users-with-filters)
- Synchronization of [standard OIDC claims](#supported-oidc-claims)

Please see [limitations](#limitations) for known issues and limitations.

## Configuration

OpenID requires configuration in Headscale and your identity provider:

- Headscale: The `oidc` section of the Headscale [configuration](configuration.md) contains all available configuration
  options along with a description and their default values.
- Identity provider: Please refer to the official documentation of your identity provider for specific instructions.
  Additionally, there might be some useful hints in the [Identity provider specific
  configuration](#identity-provider-specific-configuration) section below.

### Basic configuration

A basic configuration connects Headscale to an identity provider and typically requires:

- OpenID Connect Issuer URL from the identity provider. Headscale uses the OpenID Connect Discovery Protocol 1.0 to
  automatically obtain OpenID configuration parameters (example: `https://sso.example.com`).
- Client ID from the identity provider (example: `headscale`).
- Client secret generated by the identity provider (example: `generated-secret`).
- Redirect URI for your identity provider (example: `https://headscale.example.com/oidc/callback`).

=== "Headscale"

    ```yaml
    oidc:
      issuer: "https://sso.example.com"
      client_id: "headscale"
      client_secret: "generated-secret"
    ```

=== "Identity provider"

    - Create a new confidential client (`Client ID`, `Client secret`)
    - Add Headscale's OIDC callback URL as valid redirect URL: `https://headscale.example.com/oidc/callback`
    - Configure additional parameters to improve user experience such as: name, description, logo, …

### Enable PKCE (recommended)

Proof Key for Code Exchange (PKCE) adds an additional layer of security to the OAuth 2.0 authorization code flow by
preventing authorization code interception attacks, see: <https://datatracker.ietf.org/doc/html/rfc7636>. PKCE is
recommended and needs to be configured for Headscale and the identity provider alike:

=== "Headscale"

    ```yaml hl_lines="5-6"
    oidc:
      issuer: "https://sso.example.com"
      client_id: "headscale"
      client_secret: "generated-secret"
      pkce:
        enabled: true
    ```

=== "Identity provider"

    - Enable PKCE for the headscale client
    - Set the PKCE challenge method to "S256"

### Authorize users with filters

Headscale allows to filter for allowed users based on their domain, email address or group membership. These filters can
be helpful to apply additional restrictions and control which users are allowed to join. Filters are disabled by
default, users are allowed to join once the authentication with the identity provider succeeds. In case multiple filters
are configured, a user needs to pass all of them.

=== "Allowed domains"

    - Check the email domain of each authenticating user against the list of allowed domains and only authorize users
      whose email domain matches `example.com`.
    - A verified email address is required [unless email verification is disabled](#control-email-verification).
    - Access allowed: `alice@example.com`
    - Access denied: `bob@example.net`

    ```yaml hl_lines="5-6"
    oidc:
      issuer: "https://sso.example.com"
      client_id: "headscale"
      client_secret: "generated-secret"
      allowed_domains:
        - "example.com"
    ```

=== "Allowed users/emails"

    - Check the email address of each authenticating user against the list of allowed email addresses and only authorize
      users whose email is part of the `allowed_users` list.
    - A verified email address is required [unless email verification is disabled](#control-email-verification).
    - Access allowed: `alice@example.com`, `bob@example.net`
    - Access denied: `mallory@example.net`

    ```yaml hl_lines="5-7"
    oidc:
      issuer: "https://sso.example.com"
      client_id: "headscale"
      client_secret: "generated-secret"
      allowed_users:
        - "alice@example.com"
        - "bob@example.net"
    ```

=== "Allowed groups"

    - Use the OIDC `groups` claim of each authenticating user to get their group membership and only authorize users
      which are members in at least one of the referenced groups.
    - Access allowed: users in the `headscale_users` group
    - Access denied: users without groups, users with other groups

    ```yaml hl_lines="5-7"
    oidc:
      issuer: "https://sso.example.com"
      client_id: "headscale"
      client_secret: "generated-secret"
      scope: ["openid", "profile", "email", "groups"]
      allowed_groups:
        - "headscale_users"
    ```

### Control email verification

Headscale uses the `email` claim from the identity provider to synchronize the email address to its user profile. By
default, a user's email address is only synchronized when the identity provider reports the email address as verified
via the `email_verified: true` claim.

Unverified emails may be allowed in case an identity provider does not send the `email_verified` claim or email
verification is not required. In that case, a user's email address is always synchronized to the user profile.

```yaml hl_lines="5"
oidc:
  issuer: "https://sso.example.com"
  client_id: "headscale"
  client_secret: "generated-secret"
  email_verified_required: false
```

### Customize node expiration

The node expiration is the amount of time a node is authenticated with OpenID Connect until it expires and needs to
reauthenticate. The default node expiration can be configured via the top-level `node.expiry` setting.

=== "Customize node expiration"

    ```yaml hl_lines="2"
    node:
      expiry: 30d   # Use 0 to disable node expiration
    ```

=== "Use expiration from Access Token"

    Please keep in mind that the Access Token is typically a short-lived token that expires within a few minutes. You
    will have to configure token expiration in your identity provider to avoid frequent re-authentication.

    ```yaml hl_lines="5"
    oidc:
      issuer: "https://sso.example.com"
      client_id: "headscale"
      client_secret: "generated-secret"
      use_expiry_from_token: true
    ```

!!! tip "Expire a node and force re-authentication"

    A node can be expired immediately via:

    ```console
    headscale node expire -i <NODE_ID>
    ```

### Reference a user in the policy

You may refer to users in the Headscale policy via:

- Email address
- Username
- Provider identifier (this value is currently only available from the [API](api.md), database or directly from your
  identity provider)

!!! note "A user identifier in the policy must contain a single `@`"

    The Headscale policy requires a single `@` to reference a user. If the username or provider identifier doesn't
    already contain a single `@`, it needs to be appended at the end. For example: the Headscale username `ssmith` has
    to be written as `ssmith@` to be correctly identified as user within the policy.

    Ensure that the Headscale username itself does not end with `@`.

!!! warning "Email address or username might be updated by users"

    Many identity providers allow users to update their own profile. Depending on the identity provider and its
    configuration, the values for username or email address might change over time. This might have unexpected
    consequences for Headscale where a policy might no longer work or a user might obtain more access by hijacking an
    existing username or email address.

!!! tip "Howto use the provider identifier in the policy"

    The provider identifier uniquely identifies an OIDC user and a well-behaving identity provider guarantees that this
    value never changes for a particular user. It is usually an opaque and long string and its value is currently only
    available from the [API](api.md), database or directly from your identity provider).

    Use the [API](api.md) with the `/api/v1/user` endpoint to fetch the provider identifier (`providerId`). The value
    (be sure to append an `@` in case the provider identifier doesn't already contain an `@` somewhere) can be used
    directly to reference a user in the policy. To improve readability of the policy, one may use the `groups` section
    as an alias:

    ```json
    {
      "groups": {
        "group:alice": [
          "https://sso.example.com/oauth2/openid/59ac9125-c31b-46c5-814e-06242908cf57@"
        ]
      },
      "grants": [
        {
          "src": ["group:alice"],
          "dst": ["*"],
          "ip": ["*"]
        }
      ]
    }
    ```

## Supported OIDC claims

Headscale uses [the standard OIDC claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) to
populate and update its local user profile on each login. OIDC claims are read from the ID Token and from the UserInfo
endpoint.

| Headscale profile   | OIDC claim           | Notes / examples                                                                                  |
| ------------------- | -------------------- | ------------------------------------------------------------------------------------------------- |
| email address       | `email`              | Only verified emails are synchronized, unless `email_verified_required: false` is configured      |
| display name        | `name`               | eg: `Sam Smith`                                                                                   |
| username            | `preferred_username` | Depends on identity provider, eg: `ssmith`, `ssmith@idp.example.com`, `\\example.com\ssmith`      |
| profile picture     | `picture`            | URL to a profile picture or avatar                                                                |
| provider identifier | `iss`, `sub`         | A stable and unique identifier for a user, typically a combination of `iss` and `sub` OIDC claims |
|                     | `groups`             | [Only used to filter for allowed groups](#authorize-users-with-filters)                           |

## Limitations

- Support for OpenID Connect aims to be generic and vendor independent. It offers only limited support for quirks of
  specific identity providers.
- OIDC groups cannot be used in policy rules.
- The username provided by the identity provider needs to adhere to this pattern:
    - The username must be at least two characters long.
    - It must only contain letters, digits, hyphens, dots, underscores, and up to a single `@`.
    - The username must start with a letter.

Please see the [GitHub label "OIDC"](https://github.com/juanfont/headscale/labels/OIDC) for OIDC related issues.

## Identity provider specific configuration

!!! warning "Third-party software and services"

    This section of the documentation is specific for third-party software and services. We recommend users read the
    third-party documentation on how to configure and integrate an OIDC client. Please see the [Configuration
    section](#configuration) for a description of Headscale's OIDC related configuration settings.

Any identity provider with OpenID Connect support should "just work" with Headscale. The following identity providers
are known to work:

- [Authelia](#authelia)
- [Authentik](#authentik)
- [Kanidm](#kanidm)
- [Keycloak](#keycloak)

### Authelia

Authelia is fully supported by Headscale.

### Authentik

- Authentik is fully supported by Headscale.
- [Headscale does not support JSON Web Encryption](https://github.com/juanfont/headscale/issues/2446). Leave the field
  `Encryption Key` in the providers section unset.
- See Authentik's [Integrate with Headscale](https://integrations.goauthentik.io/networking/headscale/)

### Google OAuth

!!! warning "No username due to missing preferred_username claim"

    Google OAuth does not send the `preferred_username` claim when the `profile` scope is requested. The username in
    Headscale will be blank/not set.

In order to integrate Headscale with Google, you'll need to have a [Google Cloud
Console](https://console.cloud.google.com) account.

Google OAuth has a [verification process](https://support.google.com/cloud/answer/9110914?hl=en) if you need to have
users authenticate who are outside of your domain. If you only need to authenticate users from your domain name (ie
`@example.com`), you don't need to go through the verification process.

However if you don't have a domain, or need to add users outside of your domain, you can manually add emails via Google
Console.

#### Steps

1. Go to [Google Console](https://console.cloud.google.com) and login or create an account if you don't have one.
1. Create a project (if you don't already have one).
1. On the left hand menu, go to `APIs and services` -> `Credentials`
1. Click `Create Credentials` -> `OAuth client ID`
1. Under `Application Type`, choose `Web Application`
1. For `Name`, enter whatever you like
1. Under `Authorised redirect URIs`, add Headscale's OIDC callback URL: `https://headscale.example.com/oidc/callback`
1. Click `Save` at the bottom of the form
1. Take note of the `Client ID` and `Client secret`, you can also download it for reference if you need it.
1. [Configure Headscale following the "Basic configuration" steps](#basic-configuration). The issuer URL for Google
   OAuth is: `https://accounts.google.com`.

### Kanidm

- Kanidm is fully supported by Headscale.
- Groups for the [allowed groups filter](#authorize-users-with-filters) need to be specified with their full SPN, for
  example: `headscale_users@sso.example.com`.
- Kanidm sends the full SPN (`alice@sso.example.com`) as `preferred_username` by default. Headscale stores this value as
  username which might be confusing as the username and email fields now contain values that look like an email address.
  [Kanidm can be configured to send the short username as `preferred_username` attribute
  instead](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html#short-names):
    ```console
    kanidm system oauth2 prefer-short-username <client name>
    ```
    Once configured, the short username in Headscale will be `alice` and can be referred to as `alice@` in the policy.

### Keycloak

Keycloak is fully supported by Headscale.

#### Additional configuration to use the allowed groups filter

Keycloak has no built-in client scope for the OIDC `groups` claim. This extra configuration step is **only** needed if
you need to [authorize access based on group membership](#authorize-users-with-filters).

- Create a new client scope `groups` for OpenID Connect:
    - Configure a `Group Membership` mapper with name `groups` and the token claim name `groups`.
    - Add the mapper to at least the UserInfo endpoint.
- Configure the new client scope for your Headscale client:
    - Edit the Headscale client.
    - Search for the client scope `group`.
    - Add it with assigned type `Default`.
- [Configure the allowed groups in Headscale](#authorize-users-with-filters). How groups need to be specified depends on
  Keycloak's `Full group path` option:
    - `Full group path` is enabled: groups contain their full path, e.g. `/top/group1`
    - `Full group path` is disabled: only the name of the group is used, e.g. `group1`

### Microsoft Entra ID

In order to integrate Headscale with Microsoft Entra ID, you'll need to provision an App Registration with the correct
scopes and redirect URI.

[Configure Headscale following the "Basic configuration" steps](#basic-configuration). The issuer URL for Microsoft
Entra ID is: `https://login.microsoftonline.com/<tenant-UUID>/v2.0`. The following `extra_params` might be useful:

- `domain_hint: example.com` to use your own domain
- `prompt: select_account` to force an account picker during login

When using Microsoft Entra ID together with the [allowed groups filter](#authorize-users-with-filters), configure the
Headscale OIDC scope without the `groups` claim, for example:

```yaml
oidc:
  scope: ["openid", "profile", "email"]
```

Groups for the [allowed groups filter](#authorize-users-with-filters) need to be specified with their group ID(UUID) instead
of the group name.

## Switching OIDC providers

Headscale only supports a single OIDC provider in its configuration, but it does store the provider identifier of each user. When switching providers, this might lead to issues with existing users: all user details (name, email, groups) might be identical with the new provider, but the identifier will differ. Headscale will be unable to create a new user as the name and email will already be in use for the existing users.

At this time, you will need to manually update the `provider_identifier` column in the `users` table for each user with the appropriate value for the new provider. The identifier is built from the `iss` and `sub` claims of the OIDC ID token, for example `https://id.example.com/12340987`.

---

### Ref Policy (`docs/ref/policy.md`)

# Policy

Headscale implements a large portion of Tailscale's [policy
features](https://tailscale.com/docs/features/tailnet-policy-file), most notably access control based on
[ACLs](https://tailscale.com/docs/features/access-control/acls) and
[Grants](https://tailscale.com/docs/features/access-control/grants) or [Tailscale
SSH](https://tailscale.com/docs/features/tailscale-ssh). See [limitations](#limitations) to learn about missing features
and notable implementation differences between Headscale and Tailscale.

Headscale uses the same [huJSON](https://github.com/tailscale/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 file[^1], specify
its path in the `policy.path` key in the [configuration file](configuration.md).

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](https://tailscale.com/docs/features/tailnet-policy-file): A description of supported sections
  within the policy file along with links to syntax references for each section.
- [ACLs](https://tailscale.com/docs/features/access-control/acls): How to configure access control using ACLs.
- [Grants](https://tailscale.com/docs/features/access-control/grants): Introduction to Grants with links to [syntax
  reference](https://tailscale.com/docs/reference/syntax/grants),
  [examples](https://tailscale.com/docs/reference/examples/grants) and a [migration guide from ACLs to
  Grants](https://tailscale.com/docs/reference/migrate-acls-grants).

## Getting started

Headscale supports both [ACLs](https://tailscale.com/docs/features/access-control/acls) and
[Grants](https://tailscale.com/docs/features/access-control/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](https://tailscale.com/docs/reference/examples/acls#allow-all-default-acl) policy. This means all devices connected
to your tailnet will be able to communicate freely with each other.

```json title="policy.json"
{}
```

### Deny All

To [prevent all communication within your tailnet](https://tailscale.com/docs/reference/examples/acls#deny-all), you can
include an empty array for the `"grants"` section in your policy file.

```json title="policy.json"
{
  "grants": []
}
```

### More examples

- See our documentation on [subnet routers](routes.md#subnet-router) and [exit nodes](routes.md#exit-node) 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](https://tailscale.com/docs/reference/examples/acls)
    - [Grants examples](https://tailscale.com/docs/reference/examples/grants)
    - [SSH configuration](https://tailscale.com/docs/features/tailscale-ssh#configure-tailscale-ssh)
    - [Define a tag](https://tailscale.com/docs/features/tags#define-a-tag)

______________________________________________________________________

## Limitations

- [Device postures](https://tailscale.com/docs/features/device-posture) and the related sections such as `postures` or
  `srcPosture` aren't supported.
- [IP sets](https://tailscale.com/docs/features/tailnet-policy-file/ip-sets) aren't supported.
- A subset of [Autogroups](#autogroups) are available.

## Autogroups

Headscale supports several [Autogroups](https://tailscale.com/docs/reference/targets-and-selectors#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`](https://tailscale.com/docs/reference/targets-and-selectors#autogroupinternet)

Allows access to the internet through [exit nodes](routes.md#exit-node). Can only be used in policy destinations.

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

### [`autogroup:member`](https://tailscale.com/docs/reference/targets-and-selectors#autogrouprole)

Includes all [personal (untagged) devices](registration.md/#identity-model).

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

### [`autogroup:tagged`](https://tailscale.com/docs/reference/targets-and-selectors#autogrouptagged)

Includes all devices that [have at least one tag](registration.md/#identity-model).

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

### [`autogroup:self`](https://tailscale.com/docs/reference/targets-and-selectors#autogroupself)

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.

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

!!! warning "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`.

    ```json title="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`](https://tailscale.com/docs/reference/targets-and-selectors#other-built-in-targets)

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

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

### [`autogroup:danger-all`](https://tailscale.com/docs/reference/targets-and-selectors#autogroupdanger-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](https://tailscale.com/docs/reference/syntax/policy-file#node-attributes) allow for device-specific
configuration and attributes. At least the following node attributes are currently supported by Headscale[^2]:

- `drive:access`, `drive:share`: [Taildrive support](https://tailscale.com/docs/features/taildrive).
- `suggest-exit-node`, `suggest-exit-node-ui`: [Automatic exit node selection](routes.md#automatic-exit-node-selection).
- `nextdns:<profile>`, `nextdns:no-device-info`: [NextDNS integration](https://tailscale.com/docs/integrations/nextdns).
  Be sure to set NextDNS as global resolver in the [configuration](configuration.md).
- `magicdns-aaaa`: Respond to AAAA queries on the local [MagicDNS](https://tailscale.com/docs/features/magicdns)
  resolver at 100.100.100.100.
- `disable-ipv4`: Selectively disable IPv4 for specific nodes. This may be useful to work around [CGNat
  conflicts](https://tailscale.com/docs/reference/troubleshooting/network-configuration/cgnat-conflicts).
- `randomize-client-port`: Allocate a [random port for WireGuard
  traffic](https://tailscale.com/docs/reference/syntax/policy-file#randomizeclientport) instead of the static default
  port 41641.
- `disable-captive-portal-detection`: [Disable automatic captive portal
  detection](https://tailscale.com/docs/integrations/captive-portals#disable-captive-portal-detection).

```json title="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](#node-attributes) for a more
fine-grained configuration instead.

- `randomizeClientPort`: Allocate a [random port for WireGuard
  traffic](https://tailscale.com/docs/reference/syntax/policy-file#randomizeclientport) instead of the static default
  port 41641.

```json title="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](integration/web-ui.md) is used.

[^2]: Other key-only node attributes can be used as well. Find them in the client source code with `grep -E ' Cap ' tailcfg/nodecap/nodecap.go` or by using [GitHub code search (requires
    login)](https://github.com/search?q=repo%3Atailscale%2Ftailscale+language%3Ago+path%3Atailcfg%2Fnodecap%2Fnodecap.go+%2F+Cap+%2F&type=code).

---

### Ref Registration (`docs/ref/registration.md`)

# Registration methods

Headscale supports multiple ways to register a node. The preferred registration method depends on the identity of a node
and your use case.

## Identity model

Tailscale's identity model distinguishes between personal and tagged nodes:

- A personal node (or user-owned node) is owned by a human and typically refers to end-user devices such as laptops,
  workstations or mobile phones. End-user devices are managed by a single user.
- A tagged node (or service-based node or non-human node) provides services to the network. Common examples include web-
  and database servers. Those nodes are typically managed by a team of users. Some additional restrictions apply for
  tagged nodes, e.g. a tagged node is not allowed to [Tailscale SSH](https://tailscale.com/docs/features/tailscale-ssh)
  into a personal node.

Headscale implements Tailscale's identity model and distinguishes between personal and tagged nodes where a personal
node is owned by a Headscale user and a tagged node is owned by a tag. Tagged devices are grouped under the special user
`tagged-devices`.

## Registration methods

There are two main ways to register new nodes, [web authentication](#web-authentication) and [registration with a pre
authenticated key](#pre-authenticated-key). Both methods can be used to register personal and tagged nodes.

### Web authentication

Web authentication is the default method to register a new node. It's interactive, where the client initiates the
registration and the Headscale administrator needs to approve the new node before it is allowed to join the network. A
node can be approved with:

- Headscale CLI (described in this documentation)
- [Headscale API](api.md)
- Or delegated to an identity provider via [OpenID Connect](oidc.md)

Web authentication relies on the presence of a Headscale user. Use the `headscale users` command to create a new
user[^1]:

```console
headscale users create <USER>
```

=== "Personal devices"

    Run `tailscale up` to login your personal device:

    ```console
    tailscale up --login-server <YOUR_HEADSCALE_URL>
    ```

    Usually, a browser window with further instructions is opened. This page explains how to complete the registration
    on your Headscale server and it also prints the Auth ID required to approve the node:

    ```console
    headscale auth register --user <USER> --auth-id <AUTH_ID>
    ```

    Congrations, the registration of your personal node is complete and it should be listed as "online" in the output of
    `headscale nodes list`. The "User" column displays `<USER>` as the owner of the node.

=== "Tagged devices"

    Your Headscale user needs to be authorized to register tagged devices. This authorization is specified in the
    [`tagOwners`](https://tailscale.com/docs/reference/syntax/policy-file#tag-owners) section of the
    [policy](policy.md). A simple example looks like this:

    ```json title="The user alice can register nodes tagged with tag:server"
    {
      "tagOwners": {
        "tag:server": ["alice@"]
      },
      // more rules
    }
    ```

    Run `tailscale up` and provide at least one tag to login a tagged device:

    ```console
    tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:<TAG>
    ```

    Usually, a browser window with further instructions is opened. This page explains how to complete the registration
    on your Headscale server and it also prints the Auth ID required to approve the node:

    ```console
    headscale auth register --user <USER> --auth-id <AUTH_ID>
    ```

    Headscale checks that `<USER>` is allowed to register a node with the specified tag(s) and then transfers ownership
    of the new node to the special user `tagged-devices`. The registration of a tagged node is complete and it should be
    listed as "online" in the output of `headscale nodes list`. The "User" column displays `tagged-devices` as the owner
    of the node. See the "Tags" column for the list of assigned tags.

### Pre authenticated key

Registration with a pre authenticated key (or auth key) is a non-interactive way to register a new node. The Headscale
administrator creates a preauthkey upfront and this preauthkey can then be used to register a node non-interactively.
Its best suited for automation.

=== "Personal devices"

    A personal node is always assigned to a Headscale user. Use the `headscale users` command to create a new user[^1]:

    ```console
    headscale users create <USER>
    ```

    Create a new pre authenticated key for your user, by name or by the `<USER_ID>` that `headscale user list` shows:

    ```console
    headscale preauthkeys create --user <USER>
    ```

    The above prints a pre authenticated key with the default settings (can be used once and is valid for one hour). Use
    this auth key to register a node non-interactively:

    ```console
    tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
    ```

    Congrations, the registration of your personal node is complete and it should be listed as "online" in the output of
    `headscale nodes list`. The "User" column displays `<USER>` as the owner of the node.

=== "Tagged devices"

    Create a new pre authenticated key and provide at least one tag:

    ```console
    headscale preauthkeys create --tags tag:<TAG>
    ```

    The above prints a pre authenticated key with the default settings (can be used once and is valid for one hour). Use
    this auth key to register a node non-interactively. You don't need to provide the `--advertise-tags` parameter as
    the tags are automatically read from the pre authenticated key. Advertising a subset of the key's tags is accepted;
    any other tag is rejected:

    ```console
    tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
    ```

    The registration of a tagged node is complete and it should be listed as "online" in the output of
    `headscale nodes list`. The "User" column displays `tagged-devices` as the owner of the node. See the "Tags" column for the list of
    assigned tags.

[^1]: [Ensure that the Headscale username does not end with `@`.](oidc.md#reference-a-user-in-the-policy)

---

### Ref Routes (`docs/ref/routes.md`)

# Routes

Headscale supports route advertising and can be used to manage [subnet
routers](https://tailscale.com/docs/features/subnet-routers) and [exit
nodes](https://tailscale.com/docs/features/exit-nodes) for a tailnet.

- [Subnet routers](#subnet-router) may be used to connect an existing network such as a virtual
  private cloud or an on-premise network with your tailnet. Use a subnet router to access devices where Tailscale can't
  be installed or to gradually rollout Tailscale.
- [Exit nodes](#exit-node) can be used to route all Internet traffic for another Tailscale
  node. Use it to securely access the Internet on an untrusted Wi-Fi or to access online services that expect traffic
  from a specific IP address.

## Subnet router

The setup of a subnet router requires double opt-in, once from a subnet router and once on the control server to allow
its use within the tailnet. Optionally, use [`autoApprovers` to automatically approve routes from a subnet
router](#automatically-approve-routes-of-a-subnet-router).

### Setup a subnet router

#### Configure a node as subnet router

Register a node and advertise the routes it should handle as comma separated list:

```console
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-routes=10.0.0.0/8,192.168.0.0/24
```

If the node is already registered, it can advertise new routes or update previously announced routes with:

```console
$ sudo tailscale set --advertise-routes=10.0.0.0/8,192.168.0.0/24
```

Finally, [enable IP forwarding](#enable-ip-forwarding) to route traffic.

#### Enable the subnet router on the control server

The routes of a tailnet can be displayed with the `headscale nodes list-routes` command. A subnet router with the
hostname `myrouter` announced the IPv4 networks `10.0.0.0/8` and `192.168.0.0/24`. Those need to be approved before they
can be used.

```console
$ headscale nodes list-routes
ID | Hostname | Approved | Available      | Serving (Primary)
1  | myrouter |          | 10.0.0.0/8     |
   |          |          | 192.168.0.0/24 |
```

Approve all desired routes of a subnet router by specifying them as comma separated list:

```console
$ headscale nodes approve-routes --identifier 1 --routes 10.0.0.0/8,192.168.0.0/24
Node updated
```

The node `myrouter` can now route the IPv4 networks `10.0.0.0/8` and `192.168.0.0/24` for the tailnet.

```console
$ headscale nodes list-routes
ID | Hostname | Approved       | Available      | Serving (Primary)
1  | myrouter | 10.0.0.0/8     | 10.0.0.0/8     | 10.0.0.0/8
   |          | 192.168.0.0/24 | 192.168.0.0/24 | 192.168.0.0/24
```

#### Use the subnet router

To accept routes advertised by a subnet router on a node:

```console
$ sudo tailscale set --accept-routes
```

Please refer to the official [Tailscale
documentation](https://tailscale.com/docs/features/subnet-routers#use-your-subnet-routes-from-other-devices) for how to
use a subnet router on different operating systems.

### Restrict the use of a subnet router with a policy

The routes announced by subnet routers are available to the nodes in a tailnet. By default, without a policy enabled,
all nodes can accept and use such routes. Configure a policy to explicitly manage who can use routes.

The policy snippet below defines three hosts, a subnet router `router`, a regular node `node` and `service.example.net`
as internal service that can be reached via a route on the subnet router `router`. It allows the node `node` to access
`service.example.net` on port 80 and 443 which is reachable via the subnet router. Access to the subnet router itself is
denied.

```json title="Access the routes of a subnet router without the subnet router itself"
{
  "hosts": {
    // the router is not referenced but announces 192.168.0.0/24
    "router": "100.64.0.1/32",
    "node": "100.64.0.2/32",
    "service.example.net": "192.168.0.1/32"
  },
  "grants": [
    {
      "src": ["node"],
      "dst": ["service.example.net"],
      "ip": ["80,443"]
    }
  ]
}
```

### Automatically approve routes of a subnet router

The initial setup of a subnet router usually requires manual approval of their announced routes on the control server
before they can be used by a node in a tailnet. Headscale supports the `autoApprovers` section in a policy to automate
the approval of routes served with a subnet router.

The policy snippet below defines the tag `tag:router` owned by the user `alice`. This tag is used for `routes` in the
`autoApprovers` section. The IPv4 route `192.168.0.0/24` is automatically approved once announced by a subnet router
that advertises the tag `tag:router`.

```json title="Subnet routers tagged with tag:router are automatically approved"
{
  "tagOwners": {
    "tag:router": ["alice@"]
  },
  "autoApprovers": {
    "routes": {
      "192.168.0.0/24": ["tag:router"]
    }
  },
  "grants": [
    // more rules
  ]
}
```

Advertise the route `192.168.0.0/24` from a subnet router that also advertises the tag `tag:router` when joining the tailnet:

```console
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:router --advertise-routes 192.168.0.0/24
```

Please see the [official Tailscale
documentation](https://tailscale.com/docs/reference/syntax/policy-file#auto-approvers) for more information on auto
approvers.

## Exit node

The setup of an exit node requires double opt-in, once from an exit node and once on the control server to allow its use
within the tailnet. Optionally, use [`autoApprovers` to automatically approve an exit
node](#automatically-approve-an-exit-node-with-auto-approvers).

### Setup an exit node

#### Configure a node as exit node

Register a node and make it advertise itself as an exit node:

```console
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-exit-node
```

If the node is already registered, it can advertise exit capabilities like this:

```console
$ sudo tailscale set --advertise-exit-node
```

Finally, [enable IP forwarding](#enable-ip-forwarding) to route traffic.

#### Enable the exit node on the control server

The routes of a tailnet can be displayed with the `headscale nodes list-routes` command. An exit node can be recognized
by its announced routes: `0.0.0.0/0` for IPv4 and `::/0` for IPv6. The exit node with the hostname `myexit` is already
available, but needs to be approved:

```console
$ headscale nodes list-routes
ID | Hostname | Approved | Available | Serving (Primary)
1  | myexit   |          | 0.0.0.0/0 |
   |          |          | ::/0      |
```

For exit nodes, it is sufficient to approve either the IPv4 or IPv6 route. The other will be approved automatically.

```console
$ headscale nodes approve-routes --identifier 1 --routes 0.0.0.0/0
Node updated
```

The node `myexit` is now approved as exit node for the tailnet:

```console
$ headscale nodes list-routes
ID | Hostname | Approved  | Available | Serving (Primary)
1  | myexit   | 0.0.0.0/0 | 0.0.0.0/0 | 0.0.0.0/0
   |          | ::/0      | ::/0      | ::/0
```

#### Use the exit node

The exit node can now be used on a node with:

```console
$ sudo tailscale set --exit-node myexit
```

Please refer to the official [Tailscale documentation](https://tailscale.com/docs/features/exit-nodes#use-the-exit-node)
for how to use an exit node on different operating systems.

### Restrict the use of an exit node with a policy

An exit node is offered to all nodes in a tailnet. By default, without a policy enabled, all nodes in a tailnet can
select and use an exit node. Configure `autogroup:internet` in a policy rule to restrict who can use _any_ of the
available exit nodes.

```json title="Example use of autogroup:internet"
{
  "grants": [
    {
      "src": ["..."],
      "dst": ["autogroup:internet"],
      "ip": ["*"]
    }
  ]
}
```

### Restrict access to exit nodes per user or group

A user can use _any_ of the available exit nodes with `autogroup:internet`. Alternatively, the policy snippet below
assigns each user a specific exit node while hiding all other exit nodes. The user `alice` can only use an exit node
tagged with `tag:exit1` while user `bob` can only use an exit node tagged with `tag:exit2`.

```json title="Assign each user a dedicated exit node"
{
  "tagOwners": {
    "tag:exit1": ["alice@"],
    "tag:exit2": ["bob@"]
  },
  "grants": [
    {
      "src": ["alice@"],
      "dst": ["autogroup:internet"],
      "via": ["tag:exit1"],
      "ip": ["*"]
    },
    {
      "src": ["bob@"],
      "dst": ["autogroup:internet"],
      "via": ["tag:exit2"],
      "ip": ["*"]
    }
  ]
}
```

### Automatically approve an exit node with auto approvers

The initial setup of an exit node usually requires manual approval on the control server before it can be used by a node
in a tailnet. Headscale supports the `autoApprovers` section in a policy to automate the approval of a new exit node as
soon as it joins the tailnet.

The policy snippet below defines the tag `tag:exit` owned by the user `alice`. This tag is used for the `exitNode` entry
in the `autoApprovers` section. A new exit node that advertises the tag `tag:exit` is automatically approved:

```json title="Exit nodes tagged with tag:exit are automatically approved"
{
  "tagOwners": {
    "tag:exit": ["alice@"]
  },
  "autoApprovers": {
    "exitNode": ["tag:exit"]
  },
  "grants": [
    // more rules
  ]
}
```

Advertise a node as exit node and also advertise the tag `tag:exit` when joining the tailnet:

```console
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:exit --advertise-exit-node
```

Please see the [official Tailscale documentation](https://tailscale.com/docs/reference/syntax/policy-file#autoapprovers)
for more information on auto approvers.

### Automatic exit node selection

A Tailscale client can be configured to select one of the available exit nodes automatically. This may be useful when
there are multiple exit nodes available and clients should use an exit node without requiring a user to manually switch
between them.

The policy snippet below defines the tag `tag:exit` for exit nodes. Exit nodes with that tag are suggested to clients
with the [`suggest-exit-node` node attribute](policy.md#node-attributes). Additionally, GUI clients should display the
"Recommended" exit node selector.

```json title="Automatic exit node selection"
{
  "tagOwners": {
    "tag:exit": ["infra@"]
  },
  "nodeAttrs": [
    {
      // Suggest exit nodes tagged with tag:exit
      "target": ["tag:exit"],
      "attr": ["suggest-exit-node"]
    },
    {
      // Show "recommended" exit node selector in GUIs
      "target": ["*"],
      "attr": ["suggest-exit-node-ui"]
    }
  ],
  "grants": [
    {
      "src": ["..."],
      "dst": ["autogroup:internet"],
      "ip": ["*"]
    }
  ]
}
```

The Tailscale client can suggest a viable exit node with:

```console
$ sudo tailscale exit-node suggest
```

or automatically select one by using `auto:any` as exit node identifier:

```console
$ sudo tailscale set --exit-node=auto:any
```

Please see the [official Tailscale documentation](https://tailscale.com/docs/features/exit-nodes/auto-exit-nodes) for
more information on automatic exit node selection.

## High availability

Headscale supports high availability routing. Multiple subnet routers with overlapping routes or multiple exit nodes can
be used to provide high availability for users. If one router node goes offline, another one can serve the same routes
to clients. Please see the official [Tailscale documentation on high
availability](https://tailscale.com/docs/how-to/set-up-high-availability#subnet-router-high-availability) for details.

This feature is enabled by default when at least two nodes advertise the same prefix. See the configuration options
`node.routes.ha` in the [configuration file](configuration.md) for details.

## Troubleshooting

### Enable IP forwarding

A subnet router or exit node is routing traffic on behalf of other nodes and thus requires IP forwarding. Check the
official [Tailscale documentation](https://tailscale.com/docs/features/subnet-routers#enable-ip-forwarding) for how to
enable IP forwarding.

---

### Ref Tags (`docs/ref/tags.md`)

# Tags

Headscale supports Tailscale tags. Please read [Tailscale's tag documentation](https://tailscale.com/docs/features/tags)
to learn how tags work and how to use them.

Tags can be applied during [node registration](registration.md):

- using the `--advertise-tags` flag, see [web authentication for tagged devices](registration.md#__tabbed_1_2)
- using a tagged pre authenticated key, see [how to create and use it](registration.md#__tabbed_2_2)

Administrators can manage tags with:

- Headscale CLI
- [Headscale API](api.md)

## Common operations

### Manage tags for a node

Run `headscale nodes list` to list the tags for a node.

Use the `headscale nodes tag` command to modify the tags for a node. At least one tag is required and multiple tags can
be provided as comma separated list. The following command sets the tags `tag:server` and `tag:prod` on node with ID 1:

```console
headscale nodes tag -i 1 -t tag:server,tag:prod
```

### Convert from personal to tagged node

Use the `headscale nodes tag` command to convert a personal (user-owned) node to a tagged node:

```console
headscale nodes tag -i <NODE_ID> -t <TAG>
```

The node is now owned by the special user `tagged-devices` and has the specified tags assigned to it.

### Convert from tagged to personal node

Tagged nodes can return to personal (user-owned) nodes by re-authenticating with:

```console
tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags= --force-reauth
```

Usually, a browser window with further instructions is opened. This page explains how to complete the registration on
your Headscale server and it also prints the Auth ID required to approve the node:

```console
headscale auth register --user <USER> --auth-id <AUTH_ID>
```

All previously assigned tags get removed and the node is now owned by the user specified in the above command.

---

### Ref Tls (`docs/ref/tls.md`)

# Running the service via TLS (optional)

## Bring your own certificate

Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_key_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.

```yaml title="config.yaml"
tls_cert_path: ""
tls_key_path: ""
```

The certificate should contain the full chain, else some clients, like the Tailscale Android client, will reject it.

## Let's Encrypt / ACME

To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/), set `tls_letsencrypt_hostname` to the desired certificate hostname. This name must resolve to the IP address(es) headscale is reachable on (i.e., it must correspond to the `server_url` configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured in `tls_letsencrypt_cache_dir`. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.

```yaml title="config.yaml"
tls_letsencrypt_hostname: ""
tls_letsencrypt_listen: ":http"
tls_letsencrypt_cache_dir: ".cache"
tls_letsencrypt_challenge_type: HTTP-01
```

### Challenge types

Headscale only supports two values for `tls_letsencrypt_challenge_type`: `HTTP-01` (default) and `TLS-ALPN-01`.

#### HTTP-01

For `HTTP-01`, headscale must be reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. By default, headscale listens on port 80 on all local IPs for Let's Encrypt automated validation.

!!! warning "`listen_addr` cannot also be port 80"

    `listen_addr` and `tls_letsencrypt_listen` must not bind the same TCP socket. A common mistake is setting `listen_addr: 0.0.0.0:80` together with `tls_letsencrypt_hostname` — both endpoints try to claim port 80, and the second bind fails with `address already in use` even though no other process is involved. Headscale validates this at startup and refuses to launch with a `Fatal config error: listen_addr and tls_letsencrypt_listen would bind the same TCP socket` message. Use `listen_addr: 0.0.0.0:443` (or any non-80 port) when HTTP-01 is enabled.

If you need to change the ip and/or port used by headscale for the Let's Encrypt validation process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls_letsencrypt_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls_letsencrypt_listen`.

#### TLS-ALPN-01

For `TLS-ALPN-01`, headscale listens on the ip:port combination defined in `listen_addr`. Let's Encrypt will _only_ connect to port 443 for the validation callback, so if `listen_addr` is not set to port 443, something else (e.g. a firewall rule) will be required to forward the traffic from port 443 to the ip:port combination specified in `listen_addr`.

### Technical description

Headscale uses [autocert](https://pkg.go.dev/golang.org/x/crypto/acme/autocert), a Golang library providing [ACME protocol](https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment) verification, to facilitate certificate renewals via [Let's Encrypt](https://letsencrypt.org/about/). Certificates will be renewed automatically, and the following can be expected:

- Certificates provided from Let's Encrypt have a validity of 3 months from date issued.
- Renewals are only attempted by headscale when 30 days or less remains until certificate expiry.
- Renewal attempts by autocert are triggered at a random interval of 30-60 minutes.
- No log output is generated when renewals are skipped, or successful.

#### Checking certificate expiry

If you want to validate that certificate renewal completed successfully, this can be done either manually, or through external monitoring software. Two examples of doing this manually:

1. Open the URL for your headscale server in your browser of choice, and manually inspecting the expiry date of the certificate you receive.
1. Or, check remotely from CLI using `openssl`:

```console
$ openssl s_client -servername [hostname] -connect [hostname]:443 | openssl x509 -noout -dates
(...)
notBefore=Feb  8 09:48:26 2024 GMT
notAfter=May  8 09:48:25 2024 GMT
```

#### Log output from the autocert library

As these log lines are from the autocert library, they are not strictly generated by headscale itself.

```plaintext
acme/autocert: missing server name
```

Likely caused by an incoming connection that does not specify a hostname, for example a `curl` request directly against the IP of the server, or an unexpected hostname.

```plaintext
acme/autocert: host "[foo]" not configured in HostWhitelist
```

Similarly to the above, this likely indicates an invalid incoming request for an incorrect hostname, commonly just the IP itself.

The source code for autocert can be found [here](https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.19.0:acme/autocert/autocert.go)

---

### Setup Requirements (`docs/setup/requirements.md`)

# Requirements

Headscale should just work as long as the following requirements are met:

- A server with a public IP address for headscale. A dual-stack setup with a public IPv4 and a public IPv6 address is
  recommended.
- Headscale is served via HTTPS on port 443[^1] and [may use additional ports](#ports-in-use).
- A reasonably modern Linux or BSD based operating system.
- A dedicated local user account to run headscale.
- A little bit of command line knowledge to configure and operate headscale.

## Ports in use

The ports in use vary with the intended scenario and enabled features. Some of the listed ports may be changed via the
[configuration file](../ref/configuration.md) but we recommend to stick with the default values.

- tcp/80
    - Expose publicly: yes
    - HTTP, used by Let's Encrypt to verify ownership via the HTTP-01 challenge.
    - Only required if the built-in Let's Encrypt client with the HTTP-01 challenge is used. See [TLS](../ref/tls.md) for
      details.
- tcp/443
    - Expose publicly: yes
    - HTTPS, required to make Headscale available to Tailscale clients[^1]
    - Required if the [embedded DERP server](../ref/derp.md) is enabled
- udp/3478
    - Expose publicly: yes
    - STUN, required if the [embedded DERP server](../ref/derp.md) is enabled
- tcp/9090
    - Expose publicly: no
    - [Metrics and debug endpoint](../ref/debug.md#metrics-and-debug-endpoint)

## Assumptions

The headscale documentation and the provided examples are written with a few assumptions in mind:

- Headscale is running as system service via a dedicated local user `headscale`.
- The [configuration](../ref/configuration.md) is loaded from `/etc/headscale/config.yaml`.
- SQLite is used as database.
- The data directory for headscale (used for private keys, policy, SQLite database, …) is located in `/var/lib/headscale`.
- URLs and values that need to be replaced by the user are either denoted as `<VALUE_TO_CHANGE>` or use placeholder
  values such as `headscale.example.com`.

Please adjust to your local environment accordingly.

[^1]: The Tailscale client assumes HTTPS on port 443 in certain situations. Serving headscale either via HTTP or via
    HTTPS on a port other than 443 is possible but sticking with HTTPS on port 443 is strongly recommended for
    production setups. See [issue 2164](https://github.com/juanfont/headscale/issues/2164) for more information.

---

### Setup Upgrade (`docs/setup/upgrade.md`)

# Upgrade an existing installation

!!! tip "Required update path"

    It's required to update from one stable version to the next (e.g. 0.26.0 → 0.27.1 → 0.28.0) without skipping minor
    versions in between. You should always pick the latest available patch release.

Update an existing Headscale installation to a new version:

- Read the announcement on the [GitHub releases](https://github.com/juanfont/headscale/releases) page for the new
  version. It lists the changes of the release along with possible breaking changes and version-specific upgrade
  instructions.
- Stop Headscale
- **[Create a backup of your installation](#backup)**
- Update Headscale to the new version, preferably by following the same installation method.
- Compare and update the [configuration](../ref/configuration.md) file.
- Start Headscale

## Backup

Headscale applies database migrations during upgrades and we highly recommend to create a backup of your database before
upgrading. A full backup of Headscale depends on your individual setup, but below are some typical setup scenarios.

=== "Standard installation"

    An installation that follows our [official releases](install/official.md) setup guide uses the following paths:

    - [Configuration file](../ref/configuration.md): `/etc/headscale/config.yaml`
    - Data directory: `/var/lib/headscale`
    - SQLite as database: `/var/lib/headscale/db.sqlite`

    ```console
    TIMESTAMP=$(date +%Y%m%d%H%M%S)
    cp -aR /etc/headscale /etc/headscale.backup-$TIMESTAMP
    cp -aR /var/lib/headscale /var/lib/headscale.backup-$TIMESTAMP
    ```

=== "Container"

    An installation that follows our [container](install/container.md) setup guide uses a single source volume directory
    that contains the configuration file, data directory and the SQLite database.

    ```console
    cp -aR /path/to/headscale /path/to/headscale.backup-$(date +%Y%m%d%H%M%S)
    ```

=== "PostgreSQL"

    Please follow PostgreSQL's [Backup and Restore](https://www.postgresql.org/docs/current/backup.html) documentation
    to create a backup of your PostgreSQL database.

---

### Ref Integration Reverse Proxy (`docs/ref/integration/reverse-proxy.md`)

# Running Headscale behind a reverse proxy

!!! warning "Community documentation"

    This page is not actively maintained by the Headscale authors and is
    written by community members. It is _not_ verified by Headscale developers.

    **It might be outdated and it might miss necessary steps**.

Running Headscale behind a reverse proxy is useful when running multiple applications on the same server, and you want
to reuse the same external IP and port - usually tcp/443 for HTTPS.

Please see [limitations](#limitations) for known issues and limitations.

## Configuration

The configuration depends on the set of Headscale features you intend to use. Please have a look at the
[requirements](../../setup/requirements.md) and especially the [ports in use](../../setup/requirements.md#ports-in-use)
section to learn what a Tailscale clients expects.

The configuration examples in this documentation are basic and cover only HTTP and HTTPS traffic. Other features such as
STUN for Headscale's [embedded DERP server](../derp.md) are expected to be exposed directly or to be only available on
localhost.

### WebSocket

Tailscale clients are using a custom protocol (Tailscale Control Protocol) to communicate with a control server such as
Headscale. The reverse proxy **must** be configured to support WebSockets in order to communicate with Tailscale clients
and it needs to handle two peculiarities of the Tailscale Control Protocol:

- The POST method is used to upgrade the WebSocket connection.
- The value for the `Upgrade` header is `tailscale-control-protocol`.

### TLS

Headscale can be configured not to use TLS, leaving it to the reverse proxy to handle. Add the following configuration
values to your Headscale [configuration file](../configuration.md):

```yaml title="config.yaml" hl_lines="1"
server_url: https://<SERVER_NAME>
tls_cert_path: ""
tls_key_path: ""
```

Headscale logs `WRN listening without TLS but ServerURL does not start with http://` during startup. This is expected
and indicates that the reverse proxy is in charge of terminating TLS.

### Trusted proxies

Headscale ignores `True-Client-IP`, `X-Real-IP` and `X-Forwarded-For` headers unless the request's TCP peer matches the
`trusted_proxies` configuration option. Set this to the CIDR(s) your reverse proxy connects from so the real client IP
appears in access logs.

```yaml title="config.yaml"
trusted_proxies:
  - 127.0.0.1/32
  - ::1/128
```

The reverse proxy is responsible to replace any client-supplied `True-Client-IP`, `X-Real-IP`, `X-Forwarded-For` headers
on inbound requests with sanitized values. Headscale picks the first valid IP address supplied by headers in this order:

- `True-Client-IP`
- `X-Real-IP`
- `X-Forwarded-For`

## Limitations

- A reverse proxy adds another layer of complexity that needs to be able to handle the [Tailscale Control
  Protocol](#websocket) properly. Be sure to test your setup without a reverse proxy before raising an issue.
- STUN (used along with the [embedded DERP server](../derp.md)) requires udp/3478 to be served publicly.

## Reverse proxy specific configuration

!!! warning "Third-party software and services"

    This section of the documentation is specific for third-party software and services. We recommend users read the
    third-party documentation for a secure configuration.

This following Headscale configuration may be used as base for the various reverse proxy examples below. The following
is [assumed](../../setup/requirements.md):

- Service for Tailscale clients is served via HTTPS on port 443.
- The reverse proxy redirects HTTP to HTTPS and is terminating TLS.
- Both Headscale and the reverse proxy are running on the same host.
- [Metrics](../debug.md#metrics-and-debug-endpoint) are not proxied, those are available via localhost.

```yaml title="config.yaml" hl_lines="1"
server_url: https://<SERVER_NAME>
listen_addr: 127.0.0.1:8080
metrics_listen_addr: 127.0.0.1:9090
trusted_proxies:
  - 127.0.0.1/32
  - ::1/128
tls_cert_path: ""
tls_key_path: ""
```

### Apache

The following basic Apache configuration works with the Headscale configuration [as shown
above](#reverse-proxy-specific-configuration). Substitute placeholders and adjust the configuration as needed:

- `<SERVER_NAME>`: The server name for your instance, e.g. `headscale.example.com`
- `<PATH_TO_TLS_CERT>`: Absolute path to your TLS certificate
- `<PATH_TO_TLS_KEY>`: Absolute path to your TLS private key

```apache title="apache.conf" hl_lines="2 7 11 14-15"
<VirtualHost *:80>
  ServerName <SERVER_NAME>

  # Tailscale captive portal detection
  RedirectMatch 204 ^/generate_204$

  RedirectMatch permanent "^/(.*)$" "https://<SERVER_NAME>/$1"
</VirtualHost>

<VirtualHost *:443>
  ServerName <SERVER_NAME>

  SSLEngine On
  SSLCertificateFile <PATH_TO_TLS_CERT>
  SSLCertificateKeyFile <PATH_TO_TLS_KEY>

  RequestHeader set True-Client-IP "%{REMOTE_ADDR}s"
  RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"

  ProxyPreserveHost On
  ProxyPass / http://127.0.0.1:8080/ upgrade=any
</VirtualHost>
```

Note that `upgrade=any` is required as a parameter for `ProxyPass` so that WebSocket traffic whose `Upgrade` header
value is not equal to `WebSocket` (i. e. Tailscale Control Protocol) is forwarded correctly. See the [Apache
docs](https://httpd.apache.org/docs/current/mod/mod_proxy.html#upgrade) for more information on this.

### Caddy

The following basic Caddyfile works with the Headscale configuration [as shown
above](#reverse-proxy-specific-configuration). Substitute placeholders and adjust the configuration as needed:

- `<SERVER_NAME>`: The server name for your instance, e.g. `headscale.example.com`

```none title="Caddyfile" hl_lines="1 12"
http://<SERVER_NAME> {
	# Tailscale captive portal detection
	handle /generate_204 {
		respond 204
	}

	handle * {
		redir https://{host}{uri}
	}
}

<SERVER_NAME> {
	reverse_proxy 127.0.0.1:8080 {
		header_up True-Client-IP {remote_host}
		header_up X-Real-IP {remote_host}
	}
}
```

Caddy will [automatically](https://caddyserver.com/docs/automatic-https) provision a certificate for your
domain/subdomain, force HTTPS, and proxy WebSocket connections.

### Cloudflare

Running Headscale behind a Cloudflare Proxy or Cloudflare Tunnel is not supported and will not work as Cloudflare does
not support [WebSocket POSTs as required by the Tailscale protocol](#websocket). See [issue
1468](https://github.com/juanfont/headscale/issues/1468) for more information.

### Envoy

You need to add a new upgrade_type named `tailscale-control-protocol`. [See
details](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-upgradeconfig).

### Istio

Same as [envoy](#envoy), we can use `EnvoyFilter` to add a new upgrade_type named `tailscale-control-protocol`.

```yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: headscale-behind-istio-ingress
  namespace: istio-system
spec:
  configPatches:
    - applyTo: NETWORK_FILTER
      match:
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
      patch:
        operation: MERGE
        value:
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
            upgrade_configs:
              - upgrade_type: tailscale-control-protocol
```

### Nginx

The following basic Nginx configuration works with the Headscale configuration [as shown
above](#reverse-proxy-specific-configuration). Substitute placeholders and adjust the configuration as needed:

- `<SERVER_NAME>`: The server name for your instance, e.g. `headscale.example.com`
- `<PATH_TO_TLS_CERT>`: Absolute path to your TLS certificate
- `<PATH_TO_TLS_KEY>`: Absolute path to your TLS private key

```nginx title="nginx.conf" hl_lines="19 37 39-40"
# headscale
upstream headscale {
  zone upstreams 64K;
  server 127.0.0.1:8080 max_fails=1 fail_timeout=5s;
  keepalive 2;
}

# websocket
map $http_upgrade $connection_upgrade {
  default keep-alive;
  ''      close;
}

# http
server {
  listen 80;
  listen [::]:80;

  server_name <SERVER_NAME>;

  # Tailscale captive portal detection
  location = /generate_204 {
    return 204;
  }

  location / {
    return 301 https://$server_name$request_uri;
  }
}

# https
server {
  listen 443 ssl;
  listen [::]:443 ssl;
  http2 on;

  server_name <SERVER_NAME>;

  ssl_certificate <PATH_TO_TLS_CERT>;
  ssl_certificate_key <PATH_TO_TLS_KEY>;

  location / {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
    proxy_set_header True-Client-IP $remote_addr;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_buffering off;
    proxy_pass http://headscale;
  }
}
```

---

### Ref Integration Tools (`docs/ref/integration/tools.md`)

# Tools related to headscale

!!! warning "Community contributions"

    This page contains community contributions. The projects listed here are not
    maintained by the headscale authors and are written by community members.

This page collects third-party tools, client libraries, and scripts related to headscale.

- [headscale-operator](https://github.com/infradohq/headscale-operator)
    - 
      
    - Headscale Kubernetes Operator
- [tailscale-manager](https://github.com/singlestore-labs/tailscale-manager)
    - 
      
    - Dynamically manage Tailscale route advertisements
- [headscalebacktosqlite](https://github.com/bigbozza/headscalebacktosqlite)
    - 
      
    - Migrate headscale from PostgreSQL back to SQLite
- [headscale-pf](https://github.com/YouSysAdmin/headscale-pf)
    - 
      
    - Populates user groups based on user groups in Jumpcloud or Authentik
- [headscale-client-go](https://github.com/hibare/headscale-client-go)
    - 
      
    - A Go client implementation for the Headscale HTTP API.
- [headscale-zabbix](https://github.com/dblanque/headscale-zabbix)
    - 
      
    - A Zabbix Monitoring Template for the Headscale Service.
- [tailscale-exporter](https://github.com/adinhodovic/tailscale-exporter)
    - 
      
    - A Prometheus exporter for Headscale that provides network-level metrics using
      the Headscale API.

---

### Ref Integration Web Ui (`docs/ref/integration/web-ui.md`)

# Web interfaces for headscale

!!! warning "Community contributions"

    This page contains community contributions. The projects listed here are not
    maintained by the headscale authors and are written by community members.

Headscale doesn't provide a built-in web interface but users may pick one from the available options.

- [headscale-ui](https://github.com/gurucomputing/headscale-ui)
    - 
      
    - A web frontend for the headscale Tailscale-compatible coordination server
- [HeadscaleUi](https://github.com/simcu/headscale-ui)
    - 
      
    - A static headscale admin ui, no backend environment required
- [Headplane](https://github.com/tale/headplane)
    - 
      
    - An advanced Tailscale inspired frontend for headscale
- [headscale-admin](https://github.com/GoodiesHQ/headscale-admin)
    - 
      
    - Headscale-Admin is meant to be a simple, modern web interface for headscale
- [ouroboros](https://github.com/yellowsink/ouroboros)
    - 
      
    - Ouroboros is designed for users to manage their own devices, rather than for
      admins
- [unraid-headscale-admin](https://github.com/ich777/unraid-headscale-admin)
    - 
      
    - A simple headscale admin UI for Unraid, it offers Local (`docker exec`) and
      API Mode
- [headscale-console](https://github.com/rickli-cloud/headscale-console)
    - 
      
    - WebAssembly-based client supporting SSH, VNC and RDP with optional
      self-service capabilities
- [headscale-piying](https://github.com/wszgrcy/headscale-piying)
    - 
      
    - headscale web ui, support visual ACL configuration
- [HeadControl](https://github.com/ahmadzip/HeadControl)
    - 
      
    - Minimal Headscale admin dashboard, built with Go and HTMX
- [Headscale Manager](https://github.com/hkdone/headscalemanager)
    - 
      
    - Headscale UI for Android
- [Headscale UI](https://github.com/MunMunMiao/headscale-ui)
    - 
      
    - Headscale UI online and Self-hosting
- [Headscale Panel](https://github.com/headscale-panel/panel)
    - 
      
    - A modern Headscale management panel with a clean, network-operations-focused
      UI

You can ask for support on our [Discord server](https://discord.gg/c84AZQhmpx) in the "web-interfaces" channel.

---

### Setup Install Community (`docs/setup/install/community.md`)

# Community packages

Several Linux distributions and community members provide packages for headscale. Those packages may be used instead of
the [official releases](official.md) provided by the headscale maintainers. Such packages offer improved integration
for their targeted operating system and usually:

- setup a dedicated local user account to run headscale
- provide a default configuration
- install headscale as system service

!!! warning "Community packages might be outdated"

    The packages mentioned on this page might be outdated or unmaintained. Use the [official releases](official.md) to
    get the current stable version or to [test pre-releases](main.md).

    [](https://repology.org/project/headscale/versions)

## Arch Linux

Arch Linux offers a package for headscale, install via:

```shell
pacman -S headscale
```

## Fedora, RHEL, CentOS

A third-party repository for various RPM based distributions is available at:
<https://copr.fedorainfracloud.org/coprs/jonathanspw/headscale/>. The site provides detailed setup and installation
instructions.

## Nix, NixOS

A Nix package is available as: `headscale`. See the [NixOS package site for installation
details](https://search.nixos.org/packages?show=headscale).

## Gentoo

```shell
emerge --ask net-vpn/headscale
```

Gentoo specific documentation is available [here](https://wiki.gentoo.org/wiki/User:Maffblaster/Drafts/Headscale).

## OpenBSD

Headscale is available in ports. The port installs headscale as system service with `rc.d` and provides usage
instructions upon installation.

```shell
pkg_add headscale
```

---

### Setup Install Container (`docs/setup/install/container.md`)

# Running headscale in a container

!!! warning "Community documentation"

    This page is not actively maintained by the headscale authors and is
    written by community members. It is _not_ verified by headscale developers.

    **It might be outdated and it might miss necessary steps**.

A container runtime such as [Docker](https://www.docker.com) or [Podman](https://podman.io) is required. The container
image can be found on [Docker Hub](https://hub.docker.com/r/headscale/headscale) and [GitHub Container
Registry](https://github.com/juanfont/headscale/pkgs/container/headscale). The container image URLs are:

- [Docker Hub](https://hub.docker.com/r/headscale/headscale): `docker.io/headscale/headscale:<VERSION>`
- [GitHub Container Registry](https://github.com/juanfont/headscale/pkgs/container/headscale):
  `ghcr.io/juanfont/headscale:<VERSION>`

## Configure and run headscale

1. Create a directory on the container host to store headscale's [configuration](../../ref/configuration.md) and the SQLite database:

    ```shell
    mkdir -p ./headscale/{config,lib}
    cd ./headscale
    ```

1. Download the example configuration for your chosen version and save it as: `$(pwd)/config/config.yaml`. Adjust the
   configuration to suit your local environment. See [Configuration](../../ref/configuration.md) for details.

1. Start headscale from within the previously created `./headscale` directory:

    ```shell
    docker run \
      --name headscale \
      --detach \
      --read-only \
      --tmpfs /tmp \
      --tmpfs /var/run/headscale \
      --volume "$(pwd)/config:/etc/headscale:ro" \
      --volume "$(pwd)/lib:/var/lib/headscale" \
      --publish 127.0.0.1:8080:8080 \
      --publish 127.0.0.1:9090:9090 \
      --health-cmd "CMD headscale health" \
      docker.io/headscale/headscale:<VERSION> \
      serve
    ```

    Note: use `0.0.0.0:8080:8080` instead of `127.0.0.1:8080:8080` if you want to expose the container externally.

    This command mounts the local directories inside the container, forwards port 8080 and 9090 out of the container so
    the headscale instance becomes available and then detaches so headscale runs in the background.

    A similar configuration for `docker-compose`:

    ```yaml title="docker-compose.yaml"
    services:
      headscale:
        image: docker.io/headscale/headscale:<VERSION>
        restart: unless-stopped
        container_name: headscale
        read_only: true
        tmpfs:
          - /var/run/headscale
        ports:
          - "127.0.0.1:8080:8080"
          - "127.0.0.1:9090:9090"
        volumes:
          # Please set <HEADSCALE_PATH> to the absolute path
          # of the previously created headscale directory.
          - <HEADSCALE_PATH>/config:/etc/headscale:ro
          - <HEADSCALE_PATH>/lib:/var/lib/headscale
        command: serve
        healthcheck:
            test: ["CMD", "headscale", "health"]
    ```

1. Verify headscale is running:

    Follow the container logs:

    ```shell
    docker logs --follow headscale
    ```

    Verify running containers:

    ```shell
    docker ps
    ```

    Verify headscale is available:

    ```shell
    curl http://127.0.0.1:8080/health
    ```

Continue on the [getting started page](../../usage/getting-started.md) to register your first machine.

## Debugging headscale running in Docker

The Headscale container image is based on a distroless image that does not contain a shell or any other debug tools. If you need to debug headscale running in the Docker container, you can use the `-debug` variant, for example `docker.io/headscale/headscale:x.x.x-debug`.

### Running the debug Docker container

To run the debug Docker container, use the exact same commands as above, but replace `docker.io/headscale/headscale:x.x.x` with `docker.io/headscale/headscale:x.x.x-debug` (`x.x.x` is the version of headscale). The two containers are compatible with each other, so you can alternate between them.

### Executing commands in the debug container

The default command in the debug container is to run `headscale`, which is located at `/ko-app/headscale` inside the container.

Additionally, the debug container includes a minimalist Busybox shell.

To launch a shell in the container, use:

```shell
docker run -it docker.io/headscale/headscale:x.x.x-debug sh
```

You can also execute commands directly, such as `ls /ko-app` in this example:

```shell
docker run docker.io/headscale/headscale:x.x.x-debug ls /ko-app
```

Using `docker exec -it` allows you to run commands in an existing container.

---

### Setup Install Main (`docs/setup/install/main.md`)

# Development builds

!!! warning

    Development builds are created automatically from the latest `main` branch
    and are **not versioned releases**. They may contain incomplete features,
    breaking changes, or bugs. Use them for testing only.

Each push to `main` produces container images and cross-compiled binaries.
Container images are multi-arch (amd64, arm64) and use the same distroless
base image as official releases.

## Container images

Images are available from both Docker Hub and GitHub Container Registry, tagged
with the short commit hash of the build (e.g. `main-abc1234`):

- Docker Hub: `docker.io/headscale/headscale:main-<sha>`
- GitHub Container Registry: `ghcr.io/juanfont/headscale:main-<sha>`

To find the latest available tag, check the
[GitHub Actions workflow](https://github.com/juanfont/headscale/actions/workflows/container-main.yml)
or the [GitHub Container Registry package page](https://github.com/juanfont/headscale/pkgs/container/headscale).

For example, to run a specific development build:

```shell
docker run \
  --name headscale \
  --detach \
  --read-only \
  --tmpfs /tmp \
  --tmpfs /var/run/headscale \
  --volume "$(pwd)/config:/etc/headscale:ro" \
  --volume "$(pwd)/lib:/var/lib/headscale" \
  --publish 127.0.0.1:8080:8080 \
  --publish 127.0.0.1:9090:9090 \
  --health-cmd "CMD headscale health" \
  docker.io/headscale/headscale:main-<sha> \
  serve
```

See [Running headscale in a container](container.md) for full container setup instructions.

## Binaries

Pre-built binaries from the latest successful build on `main` are available
via [nightly.link](https://nightly.link/juanfont/headscale/workflows/container-main/main):

| OS    | Arch  | Download                                                                                                                   |
| ----- | ----- | -------------------------------------------------------------------------------------------------------------------------- |
| Linux | amd64 | [headscale-linux-amd64](https://nightly.link/juanfont/headscale/workflows/container-main/main/headscale-linux-amd64.zip)   |
| Linux | arm64 | [headscale-linux-arm64](https://nightly.link/juanfont/headscale/workflows/container-main/main/headscale-linux-arm64.zip)   |
| macOS | amd64 | [headscale-darwin-amd64](https://nightly.link/juanfont/headscale/workflows/container-main/main/headscale-darwin-amd64.zip) |
| macOS | arm64 | [headscale-darwin-arm64](https://nightly.link/juanfont/headscale/workflows/container-main/main/headscale-darwin-arm64.zip) |

After downloading and extracting the archive, make the binary executable and follow the
[standalone binary installation](official.md#using-standalone-binaries-advanced)
instructions for setting up the service.

---

### Setup Install Official (`docs/setup/install/official.md`)

# Official releases

Official releases for headscale are available as binaries for various platforms and DEB packages for Debian and Ubuntu.
Both are available on the [GitHub releases page](https://github.com/juanfont/headscale/releases).

## Using packages for Debian/Ubuntu (recommended)

It is recommended to use our DEB packages to install headscale on a Debian based system as those packages configure a
local user to run headscale, provide a default configuration and ship with a systemd service file. Supported
distributions are Ubuntu 22.04 or newer, Debian 12 or newer.

1. Download the [latest headscale package](https://github.com/juanfont/headscale/releases/latest) for your platform (`.deb` for Ubuntu and Debian).

    ```shell
    HEADSCALE_VERSION="" # See above URL for latest version, e.g. "X.Y.Z" (NOTE: do not add the "v" prefix!)
    HEADSCALE_ARCH="" # Your system architecture, e.g. "amd64"
    wget --output-document=headscale.deb \
     "https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb"
    ```

1. Install headscale:

    ```shell
    sudo apt install ./headscale.deb
    ```

1. [Configure headscale by editing the configuration file](../../ref/configuration.md). An up-to date example
   configuration file is also available in `/usr/share/doc/headscale/examples/config-example.yaml`:

    ```shell
    sudo nano /etc/headscale/config.yaml
    ```

1. Restart headscale to pick up configuration changes:

    ```shell
    sudo systemctl restart headscale
    ```

1. Verify that headscale is running as intended:

    ```shell
    sudo systemctl status headscale
    ```

Continue on the [getting started page](../../usage/getting-started.md) to register your first machine.

## Using standalone binaries (advanced)

!!! warning "Advanced"

    This installation method is considered advanced as one needs to take care of the local user and the systemd
    service themselves. If possible, use the [DEB packages](#using-packages-for-debianubuntu-recommended) or a
    [community package](community.md) instead.

This section describes the installation of headscale according to the [Requirements and
assumptions](../requirements.md#assumptions). Headscale is run by a dedicated local user and the service itself is
managed by systemd.

1. Download the latest [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases):

    ```shell
    sudo wget --output-document=/usr/bin/headscale \
    https://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>
    ```

1. Make `headscale` executable:

    ```shell
    sudo chmod +x /usr/bin/headscale
    ```

1. Add a dedicated local user to run headscale:

    ```shell
    sudo useradd \
     --create-home \
     --home-dir /var/lib/headscale/ \
     --system \
     --user-group \
     --shell /usr/sbin/nologin \
     headscale
    ```

1. Download the example configuration for your chosen version and save it as: `/etc/headscale/config.yaml`. Adjust the
   configuration to suit your local environment. See [Configuration](../../ref/configuration.md) for details.

    ```shell
    sudo mkdir -p /etc/headscale
    sudo nano /etc/headscale/config.yaml
    ```

1. Copy [headscale's systemd service file](https://github.com/juanfont/headscale/blob/main/packaging/systemd/headscale.service)
   to `/etc/systemd/system/headscale.service` and adjust it to suit your local setup. The following parameters likely need
   to be modified: `ExecStart`, `WorkingDirectory`, `ReadWritePaths`.

1. In `/etc/headscale/config.yaml`, override the default `headscale` unix socket with a path that is writable by the
   `headscale` user or group:

    ```yaml title="config.yaml"
    unix_socket: /var/run/headscale/headscale.sock
    ```

1. Reload systemd to load the new configuration file:

    ```shell
    systemctl daemon-reload
    ```

1. Enable and start the new headscale service:

    ```shell
    systemctl enable --now headscale
    ```

1. Verify that headscale is running as intended:

    ```shell
    systemctl status headscale
    ```

Continue on the [getting started page](../../usage/getting-started.md) to register your first machine.

---

### Setup Install Source (`docs/setup/install/source.md`)

# Build from source

!!! warning "Community documentation"

    This page is not actively maintained by the headscale authors and is
    written by community members. It is _not_ verified by headscale developers.

    **It might be outdated and it might miss necessary steps**.

Headscale can be built from source using the latest version of [Go](https://golang.org) and [Buf](https://buf.build)
(Protobuf generator). See the [Contributing section in the GitHub
README](https://github.com/juanfont/headscale#contributing) for more information.

## OpenBSD

### Install from source

```shell
# Install prerequisites
pkg_add go git

git clone https://github.com/juanfont/headscale.git

cd headscale

# optionally checkout a release
# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest
# option b. get latest tag, this may be a beta release
latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)

git checkout $latestTag

go build -ldflags="-s -w -X github.com/juanfont/headscale/hscontrol/types.Version=$latestTag" -X github.com/juanfont/headscale/hscontrol/types.GitCommitHash=HASH" github.com/juanfont/headscale

# make it executable
chmod a+x headscale

# copy it to /usr/local/sbin
cp headscale /usr/local/sbin
```

### Install from source via cross compile

```shell
# Install prerequisites
# 1. go v1.27+: see the go directive in go.mod for the exact minimum
# 2. gmake: Makefile in the headscale repo is written in GNU make syntax

git clone https://github.com/juanfont/headscale.git

cd headscale

# optionally checkout a release
# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest
# option b. get latest tag, this may be a beta release
latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)

git checkout $latestTag

make build GOOS=openbsd

# copy headscale to openbsd machine and put it in /usr/local/sbin
```

---

### Usage Connect Android (`docs/usage/connect/android.md`)

# Connecting an Android client

This documentation has the goal of showing how a user can use the official Android [Tailscale](https://tailscale.com) client with headscale.

## Installation

Install the official Tailscale Android client from the [Google Play Store](https://play.google.com/store/apps/details?id=com.tailscale.ipn) or [F-Droid](https://f-droid.org/packages/com.tailscale.ipn/).

## Connect via web authentication

- Open the app and select the settings menu in the upper-right corner
- Tap on `Accounts`
- In the kebab menu icon (three dots) in the upper-right corner select `Use an alternate server`
- Enter your server URL (e.g `https://headscale.example.com`) and follow the instructions
- The client connects automatically as soon as the node registration is complete on headscale. Until then, nothing is
  visible in the server logs.

## Connect using a pre authenticated key

- Open the app and select the settings menu in the upper-right corner
- Tap on `Accounts`
- In the kebab menu icon (three dots) in the upper-right corner select `Use an alternate server`
- Enter your server URL (e.g `https://headscale.example.com`). If login prompts open, close it and continue
- Open the settings menu in the upper-right corner
- Tap on `Accounts`
- In the kebab menu icon (three dots) in the upper-right corner select `Use an auth key`
- Enter your [preauthkey generated from headscale](../../ref/registration.md#pre-authenticated-key)
- If needed, tap `Log in` on the main screen. You should now be connected to your headscale.

---

### Usage Connect Apple (`docs/usage/connect/apple.md`)

# Connecting an Apple client

This documentation has the goal of showing how a user can use the official iOS and macOS [Tailscale](https://tailscale.com) clients with headscale.

!!! info "Instructions on your headscale instance"

    An endpoint with information on how to connect your Apple device
    is also available at `/apple` on your running instance.

## iOS

### Installation

Install the official Tailscale iOS client from the [App Store](https://apps.apple.com/app/tailscale/id1470499037).

### Configuring the headscale URL

- Open the Tailscale app
- Click the account icon in the top-right corner and select `Log in…`.
- Tap the top-right options menu button and select `Use custom coordination server`.
- Enter your instance url (e.g `https://headscale.example.com`)
- Enter your credentials and log in. Headscale should now be working on your iOS device.

## macOS

### Installation

Choose one of the available [Tailscale clients for macOS](https://tailscale.com/docs/concepts/macos-variants) and
install it.

### Configuring the headscale URL

#### Command line

Use Tailscale's login command to connect with your headscale instance (e.g `https://headscale.example.com`):

```
tailscale login --login-server <YOUR_HEADSCALE_URL>
```

#### GUI

- Option + Click the Tailscale icon in the menu and hover over the Debug menu
- Under `Custom Login Server`, select `Add Account...`
- Enter the URL of your headscale instance (e.g `https://headscale.example.com`) and press `Add Account`
- Follow the login procedure in the browser

## tvOS

### Installation

Install the official Tailscale tvOS client from the [App Store](https://apps.apple.com/app/tailscale/id1470499037).

!!! danger

    **Don't** sign in to the Tailscale App before the headscale URL is configured!

### Configuring the headscale URL

- Open Tailscale
- Click the button `Install VPN configuration` and confirm the appearing popup by clicking the `Allow` button, but don't sign in yet
- Open Settings (the Apple tvOS settings) > Apps > Tailscale
- Under `ALTERNATE COORDINATION SERVER URL`, select `URL`
- Enter the URL of your headscale instance (e.g `https://headscale.example.com`) and press `OK`
- Return to the tvOS Home screen and open Tailscale
- Scan the QR code and follow the login procedure

---

### Usage Connect Windows (`docs/usage/connect/windows.md`)

# Connecting a Windows client

This documentation has the goal of showing how a user can use the official Windows [Tailscale](https://tailscale.com) client with headscale.

!!! info "Instructions on your headscale instance"

    An endpoint with information on how to connect your Windows device
    is also available at `/windows` on your running instance.

## Installation

Download the [Official Windows Client](https://tailscale.com/download/windows) and install it.

## Configuring the headscale URL

Open a Command Prompt or Powershell and use Tailscale's login command to connect with your headscale instance (e.g
`https://headscale.example.com`):

```
tailscale login --login-server <YOUR_HEADSCALE_URL>
```

Follow the instructions in the opened browser window to finish the configuration.

## Troubleshooting

### Unattended mode

By default, Tailscale's Windows client is only running when the user is logged in. If you want to keep Tailscale running
all the time, please enable "Unattended mode":

- Click on the Tailscale tray icon and select `Preferences`
- Enable `Run unattended`
- Confirm the "Unattended mode" message

See also [Keep Tailscale running when I'm not logged in to my
computer](https://tailscale.com/docs/how-to/run-unattended).

### Failing node registration

If you are seeing repeated messages like:

```
[GIN] 2022/02/10 - 16:39:34 | 200 |    1.105306ms |       127.0.0.1 | POST     "/machine/redacted"
```

in your headscale output, turn on `DEBUG` logging and look for:

```
2022-02-11T00:59:29Z DBG Machine registration has expired. Sending a authurl to register machine=redacted
```

This typically means that the registry keys above was not set appropriately.

To reset and try again, it is important to do the following:

1. Shut down the Tailscale service (or the client running in the tray)
1. Delete Tailscale Application data folder, located at `C:\Users\<USERNAME>\AppData\Local\Tailscale` and try to connect again.
1. Ensure the Windows node is deleted from headscale (to ensure fresh setup)
1. Start Tailscale on the Windows machine and retry the login.

---

--- METRICS ---
- Files Extracted: 34
- Estimated Token Budget: ~37143 tokens
- Canonical Reference: https://codewiki.google/github.com/juanfont/headscale
