Technical Documentation: juanfont/headscale
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (README.md)
An open source, self-hosted implementation of the Tailscale control server.
Join our Discord server 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:
What is Tailscale
Tailscale is a modern VPN built on top of
Wireguard.
It works like an overlay network
between the computers of your networks - using
NAT traversal.
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) 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 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.
Client OS support
Please see "Client and operating system support" in the documentation.
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.
For NixOS users, a module is available in nix/.
Builds from `main`
Development builds from the main branch are available as container images and
binaries. See the development builds
documentation for details.
Talks
- Fosdem 2026 (video): Headscale & Tailscale: The complementary open source clone
- presented by Kristoffer Dalby
- Fosdem 2023 (video): Headscale: How we are using integration testing to reimplement Tailscale
- 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 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 file.
Have also a look at our AI_POLICY.md.
Requirements
To contribute to headscale you would need the latest version of Go
and Buf (Protobuf generator).
We recommend using Nix 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 and
formatted with golines (width 88) andgofumpt.
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 and
formatted with clang-format.
The docs are formatted with mdformat.
The rest (Markdown, YAML, etc) is formatted with prettier.
Check out the .golangci.yaml and Makefile to see the specific configuration.
Install development tools
- Go
- Buf
- Protobuf tools
Install and activate:
nix developTesting 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:
make generateNote: Please check in changes from gen/ in a separate commit to make it easier to review.
To run the tests:
make testTo build the program:
make buildDevelopment 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):
nix develop
make test
make buildWith your own dependencies:
make test
make buildThe Makefile will warn you if any required tools are missing and suggest running nix develop. Run make help to see all available targets.
Contributors
Made with 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.
Join our Discord server for a chat and community support.
Design goal
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. 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 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 for more information.
About
Headscale is maintained by Kristoffer Dalby and Juan Font.
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 toolheadscale.
!!! 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. 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 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, Apple or Windows devices. Read
registration methods 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:
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 thetailscale up command:
tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>[^1]: Ensure that the Headscale username does not end with @.
Requirements (`docs/requirements.txt`)
mike~=2.2
mkdocs-include-markdown-plugin~=7.3
mkdocs-macros-plugin~=1.5
mkdocs-materialx[imaging]=10.2=0.8
mkdocs-minify-plugin
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 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 and /windows on your headscale for more information) |
| Android | Yes (see docs for more information) |
| macOS | Yes (see docs and /apple on your headscale for more information) |
| iOS | Yes (see docs and /apple on your headscale for more information) |
| tvOS | Yes (see docs 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 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 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.
Have a look at our current plan 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 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.
For convenience, we also build container images with headscale. But please be aware that
we don't officially support deploying headscale using Docker. On our Discord server
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 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 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:
An environment with 1000 servers
- they rarely "move" (change their endpoints)
- new nodes are added rarely
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 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? 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 on how to configure various reverse proxies, and a
dedicated "reverse-proxy-issues" channel on our Discord server 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 oftailscale 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 oftailscale 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 commandheadscale policy check --file policy.jsonto 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:yaml
prefixes: v4: 100.64.0.0/10 v6: fd7a:115c:a1e0::/48 - Update the
nodes.ipv4andnodes.ipv6columns 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:sqlUPDATE nodes SET ipv4=concat('100.64.', id/256, '.', id%256), ipv6=concat('fd7a:115c:a1e0::', format('%x', id)); - Update the policy 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 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 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, astailscale down would. It stays off until the attribute is removed and tailscale up is run. A client started withTS_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=trueor - by passing the flag
--no-logs-no-supporttotailscaledor - 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:
- Full "base" support of Tailscale's features
- Node registration
- DNS
- File sharing
- Tags
- Routes
- Dual stack (IPv4 and IPv6)
- Ephemeral nodes
- Embedded DERP server
- Peer relays
- Policy (GitHub label "policy")
- ACLs
- Grants
- Some Autogroups
- Auto approvers for subnet
routers and exit
nodes - Tailscale SSH
- Node attributes
- Tests and
sshTests
- Node registration using Single-Sign-On (OpenID Connect) (GitHub label "OIDC")
- Basic registration
- Update user profile from identity provider
- OIDC groups cannot be used in ACLs
- Funnel (#1040)
- Serve (#1234)
- Network flow logs (#1687)
About Help (`docs/about/help.md`)
Getting help
Join our Discord server for announcements and community support.
Please report bugs via GitHub issues
About Releases (`docs/about/releases.md`)
Releases
All headscale releases are available on the GitHub release page. 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 and
GitHub Container Registry.
An Atom/RSS feed of headscale releases is available here.
See the "announcements" channel on our Discord server 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. Thank you!
Ref Api (`docs/ref/api.md`)
API
Headscale provides a HTTP REST API which may be used to integrate a web
interface, remote control Headscale 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:
headscale apikeys createCopy 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:
headscale apikeys listand to expire an API key:
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 with the HTTP
Authorization: Bearer <API_KEY>header.
Start by creating an API key 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 thetailscale/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:
headscale oauth-clients create --scope auth_keys --tag tag:ciBuild the auth key from the secret:
- Swap the prefix:
hskey-client-…becomestskey-client-…. The client only
runs the exchange fortskey-client-; Headscale accepts both. - Append
?baseURL=<your Headscale URL>. - Set
--advertise-tags. Each tag must exist in the policy'stagOwnersand be
one of the OAuth client's tags, or owned by one of them.
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 takesfile:/path/to/secret):
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:
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/tailscaletsnet (TS_CLIENT_SECRET works too); import tailscale.com/feature/oauthkey:
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 %}
- 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 to authenticate with the Headscale server.
Setup remote control
Download the
headscalebinary from GitHub's release page. Make
sure to use the same version as on the server.Put the binary somewhere in your
PATH, e.g./usr/local/bin/headscaleMake
headscaleexecutable:chmod +x /usr/local/bin/headscaleCreate an API key on the Headscale server.
Provide the connection parameters for the remote Headscale server either via a minimal YAML configuration file or
via environment variables:=== "Minimal YAML configuration file"
text```yaml title="config.yaml" cli: address: <HEADSCALE_URL> api_key: <API_KEY> ```=== "Environment variables"
text```shell export HEADSCALE_CLI_ADDRESS="<HEADSCALE_URL>" export HEADSCALE_CLI_API_KEY="<API_KEY>" ```This instructs the
headscalebinary 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 behttps.Test the connection by listing all nodes:
shellheadscale nodes listYou 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: truein the configuration file or by settingHEADSCALE_CLI_INSECURE=1via 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.yamlin 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
- the command line flag
- Validate the configuration file with:
headscale configtest
!!! example "Get the example configuration from the GitHub repository"
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. 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:
tailscale netcheck - Get the client status:
tailscale status --json - Get DNS status:
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.
log:
# Valid log levels: panic, fatal, error, warn, info, debug, trace
level: debugDatabase 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.
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: debugMetrics 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://: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://:9090/debug/` in your web browser.Ref Derp (`docs/ref/derp.md`)
DERP
A DERP (Designated Encrypted Relay for Packets) server 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. The
following sections only use a few of the available settings, check the example configuration 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:
derp:
server:
enabled: true
ipv4: 198.51.100.1
ipv6: 2001:db8::1Keep in mind that additional ports are needed to run a DERP server. 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 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 offered by Tailscale Inc. To only use Headscale's embedded
DERP server, disable the loading of the default DERP map:
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. 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.
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.
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 is used.
Ref Dns (`docs/ref/dns.md`)
DNS
Headscale supports most DNS features from Tailscale. DNS related settings can be configured
within the dns section of the configuration file.
Setting extra DNS records
Headscale allows to set extra DNS records which are made available via
MagicDNS. Extra DNS records can be configured either via static entries
in the configuration file or from a JSON file that Headscale continuously watches for changes:
- Use the
dns.extra_recordsoption in the configuration file 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 optiondns.extra_records_pathin the configuration file 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).Configure extra DNS records using one of the available configuration options:
=== "Static entries, via
dns.extra_records"text```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"text```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.Verify that DNS records are properly set using the DNS querying tool of your choice:
=== "Query with dig"
text```console dig +short grafana.myvpn.example.com 100.64.0.3 ```=== "Query with drill"
text```console drill -Q grafana.myvpn.example.com 100.64.0.3 ```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
- Authorization based on a user's domain, email address or group membership
- Synchronization of standard OIDC claims
Please see limitations for known issues and limitations.
Configuration
OpenID requires configuration in Headscale and your identity provider:
- Headscale: The
oidcsection of the Headscale configuration 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 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: `[email protected]`
- Access denied: `[email protected]`
```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: `[email protected]`, `[email protected]`
- Access denied: `[email protected]`
```yaml hl_lines="5-7"
oidc:
issuer: "https://sso.example.com"
client_id: "headscale"
client_secret: "generated-secret"
allowed_users:
- "[email protected]"
- "[email protected]"
```=== "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.
oidc:
issuer: "https://sso.example.com"
client_id: "headscale"
client_secret: "generated-secret"
email_verified_required: falseCustomize 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, 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 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, [email protected], \\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 |
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" 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 is fully supported by Headscale.
Authentik
- Authentik is fully supported by Headscale.
- Headscale does not support JSON Web Encryption. Leave the field
Encryption Keyin the providers section unset. - See Authentik's Integrate with 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 account.
Google OAuth has a verification process 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
- Go to Google Console and login or create an account if you don't have one.
- Create a project (if you don't already have one).
- On the left hand menu, go to
APIs and services->Credentials - Click
Create Credentials->OAuth client ID - Under
Application Type, chooseWeb Application - For
Name, enter whatever you like - Under
Authorised redirect URIs, add Headscale's OIDC callback URL:https://headscale.example.com/oidc/callback - Click
Saveat the bottom of the form - Take note of the
Client IDandClient secret, you can also download it for reference if you need it. - Configure Headscale following the "Basic configuration" steps. The issuer URL for Google
OAuth is:https://accounts.google.com.
Kanidm
- Kanidm is fully supported by Headscale.
- Groups for the allowed groups filter need to be specified with their full SPN, for
example:[email protected]. - Kanidm sends the full SPN (
[email protected]) aspreferred_usernameby 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 aspreferred_usernameattribute
instead:Once configured, the short username in Headscale will beconsolekanidm system oauth2 prefer-short-username <client name>aliceand can be referred to asalice@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.
- Create a new client scope
groupsfor OpenID Connect:- Configure a
Group Membershipmapper with namegroupsand the token claim namegroups. - Add the mapper to at least the UserInfo endpoint.
- Configure a
- 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. How groups need to be specified depends on
Keycloak'sFull group pathoption:Full group pathis enabled: groups contain their full path, e.g./top/group1Full group pathis 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. 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.comto use your own domainprompt: select_accountto force an account picker during login
When using Microsoft Entra ID together with the allowed groups filter, configure the
Headscale OIDC scope without the groups claim, for example:
oidc:
scope: ["openid", "profile", "email"]Groups for the allowed groups filter 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, most notably access control based on
ACLs and
Grants or Tailscale
SSH. See limitations to learn about missing features
and notable implementation differences between Headscale and Tailscale.
Headscale uses the same huJSON based file format as Tailscale. By default, no
policy is loaded which means that Headscale allows all traffic between nodes. To start using a policy file[^1], specify
its path in the policy.path key in the configuration file.
Headscale needs to be reloaded to pick up changes to the policy file. Either reload Headscale via its systemd service
(sudo systemctl reload headscale) or by sending a SIGHUP signal (sudo kill -HUP $(pidof headscale)) to the main
process. Headscale logs the result of policy processing after each reload.
Please have a look at Tailscale's policy related documentation to learn more:
- Tailscale policy file: A description of supported sections
within the policy file along with links to syntax references for each section. - ACLs: How to configure access control using ACLs.
- Grants: Introduction to Grants with links to syntax
reference,
examples and a migration guide from ACLs to
Grants.
Getting started
Headscale supports both ACLs and
Grants to write an access control policy. We recommend the
use of Grants since ACLs are considered legacy and will not receive new features by Tailscale.
Allow All
If you define a policy file but completely omit the "acls" or "grants" section, Headscale will default to an allow
all policy. This means all devices connected
to your tailnet will be able to communicate freely with each other.
{}Deny All
To prevent all communication within your tailnet, you can
include an empty array for the "grants" section in your policy file.
{
"grants": []
}More examples
- See our documentation on subnet routers and exit nodes to learn how
to restrict their use or how to automatically approve them. - The Tailscale documentation provides a large collection of configuration examples:
Limitations
- Device postures and the related sections such as
posturesorsrcPosturearen't supported. - IP sets aren't supported.
- A subset of Autogroups are available.
Autogroups
Headscale supports several Autogroups that
automatically include users, destinations, or devices with specific properties. Autogroups provide a convenient way to
write policy rules without manually listing individual users or devices.
[`autogroup:internet`](https://tailscale.com/docs/reference/targets-and-selectors#autogroupinternet)
Allows access to the internet through exit nodes. Can only be used in policy destinations.
{
"grants": [
{
"src": ["alice@"],
"dst": ["autogroup:internet"],
"ip": ["*"]
}
]
}[`autogroup:member`](https://tailscale.com/docs/reference/targets-and-selectors#autogrouprole)
Includes all personal (untagged) devices.
{
"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.
{
"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.
{
"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.
{
"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 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.suggest-exit-node,suggest-exit-node-ui: Automatic exit node selection.nextdns:,nextdns:no-device-info: NextDNS integration.
Be sure to set NextDNS as global resolver in the configuration.magicdns-aaaa: Respond to AAAA queries on the local MagicDNS
resolver at 100.100.100.100.disable-ipv4: Selectively disable IPv4 for specific nodes. This may be useful to work around CGNat
conflicts.randomize-client-port: Allocate a random port for WireGuard
traffic instead of the static default
port 41641.disable-captive-portal-detection: Disable automatic captive portal
detection.
{
"nodeAttrs": [
{
// Enable MagicDNS AAAA records for all nodes
"target": ["*"]
"attr": ["magicdns-aaaa"]
}
]
}Network-wide policy options
The following options are applied for the entire tailnet. Consider node attributes for a more
fine-grained configuration instead.
randomizeClientPort: Allocate a random port for WireGuard
traffic instead of the static default
port 41641.
{
// Use a random WireGuard port for the entire tailnet
"randomizeClientPort": true
}[^1]: Headscale also allows to store the policy in the database. This is typically only required in case a web
interface is used.
[^2]: Other key-only node attributes can be used as well. Find them in the client source code with grep -E ' Cap ' tailcfg/nodecap/nodecap.go or by using GitHub code search (requires
login).
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
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 usertagged-devices.
Registration methods
There are two main ways to register new nodes, web authentication and registration with a 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
- Or delegated to an identity provider via OpenID Connect
Web authentication relies on the presence of a Headscale user. Use the headscale users command to create a new
user[^1]:
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 `` 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 `` 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 `` 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 `` 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 @.
Ref Routes (`docs/ref/routes.md`)
Routes
Headscale supports route advertising and can be used to manage subnet
routers and exit
nodes for a tailnet.
- Subnet routers 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 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.
Setup a subnet router
Configure a node as subnet router
Register a node and advertise the routes it should handle as comma separated list:
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-routes=10.0.0.0/8,192.168.0.0/24If the node is already registered, it can advertise new routes or update previously announced routes with:
$ sudo tailscale set --advertise-routes=10.0.0.0/8,192.168.0.0/24Finally, 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.
$ 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:
$ headscale nodes approve-routes --identifier 1 --routes 10.0.0.0/8,192.168.0.0/24
Node updatedThe node myrouter can now route the IPv4 networks 10.0.0.0/8 and 192.168.0.0/24 for the tailnet.
$ 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/24Use the subnet router
To accept routes advertised by a subnet router on a node:
$ sudo tailscale set --accept-routesPlease refer to the official Tailscale
documentation 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 accessservice.example.net on port 80 and 443 which is reachable via the subnet router. Access to the subnet router itself is
denied.
{
"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 theautoApprovers section. The IPv4 route 192.168.0.0/24 is automatically approved once announced by a subnet router
that advertises the tag tag:router.
{
"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:
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:router --advertise-routes 192.168.0.0/24Please see the official Tailscale
documentation 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.
Setup an exit node
Configure a node as exit node
Register a node and make it advertise itself as an exit node:
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-exit-nodeIf the node is already registered, it can advertise exit capabilities like this:
$ sudo tailscale set --advertise-exit-nodeFinally, 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:
$ 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.
$ headscale nodes approve-routes --identifier 1 --routes 0.0.0.0/0
Node updatedThe node myexit is now approved as exit node for the tailnet:
$ 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 | ::/0Use the exit node
The exit node can now be used on a node with:
$ sudo tailscale set --exit-node myexitPlease refer to the official Tailscale documentation
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.
{
"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.
{
"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:
{
"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:
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:exit --advertise-exit-nodePlease see the official Tailscale documentation
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. Additionally, GUI clients should display the
"Recommended" exit node selector.
{
"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:
$ sudo tailscale exit-node suggestor automatically select one by using auto:any as exit node identifier:
$ sudo tailscale set --exit-node=auto:anyPlease see the official Tailscale documentation 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 for details.
This feature is enabled by default when at least two nodes advertise the same prefix. See the configuration optionsnode.routes.ha in the configuration file 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 for how to
enable IP forwarding.
Ref Tags (`docs/ref/tags.md`)
Tags
Headscale supports Tailscale tags. Please read Tailscale's tag documentation
to learn how tags work and how to use them.
Tags can be applied during node registration:
- using the
--advertise-tagsflag, see web authentication for tagged devices - using a tagged pre authenticated key, see how to create and use it
Administrators can manage tags with:
- Headscale CLI
- Headscale API
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:
headscale nodes tag -i 1 -t tag:server,tag:prodConvert from personal to tagged node
Use the headscale nodes tag command to convert a personal (user-owned) node to a tagged node:
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:
tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags= --force-reauthUsually, 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:
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.
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, 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.
tls_letsencrypt_hostname: ""
tls_letsencrypt_listen: ":http"
tls_letsencrypt_cache_dir: ".cache"
tls_letsencrypt_challenge_type: HTTP-01Challenge 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, a Golang library providing ACME protocol verification, to facilitate certificate renewals via Let's Encrypt. 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:
- Open the URL for your headscale server in your browser of choice, and manually inspecting the expiry date of the certificate you receive.
- Or, check remotely from CLI using
openssl:
$ 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 GMTLog output from the autocert library
As these log lines are from the autocert library, they are not strictly generated by headscale itself.
acme/autocert: missing server nameLikely 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.
acme/autocert: host "[foo]" not configured in HostWhitelistSimilarly 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
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.
- 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 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 for
details.
- tcp/443
- Expose publicly: yes
- HTTPS, required to make Headscale available to Tailscale clients[^1]
- Required if the embedded DERP server is enabled
- udp/3478
- Expose publicly: yes
- STUN, required if the embedded DERP server is enabled
- tcp/9090
- Expose publicly: no
- 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 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 asheadscale.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 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 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
- Update Headscale to the new version, preferably by following the same installation method.
- Compare and update the configuration 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 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 and especially the 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 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
Upgradeheader istailscale-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:
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 thetrusted_proxies configuration option. Set this to the CIDR(s) your reverse proxy connects from so the real client IP
appears in access logs.
trusted_proxies:
- 127.0.0.1/32
- ::1/128The 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-IPX-Real-IPX-Forwarded-For
Limitations
- A reverse proxy adds another layer of complexity that needs to be able to handle the Tailscale Control
Protocol properly. Be sure to test your setup without a reverse proxy before raising an issue. - STUN (used along with the embedded DERP server) 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:
- 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 are not proxied, those are available via localhost.
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. Substitute placeholders and adjust the configuration as needed:
<SERVER_NAME>: The server name for your instance, e.g.headscale.example.com- ``: Absolute path to your TLS certificate
- ``: Absolute path to your TLS private key
<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 for more information on this.
Caddy
The following basic Caddyfile works with the Headscale configuration as shown
above. Substitute placeholders and adjust the configuration as needed:
<SERVER_NAME>: The server name for your instance, e.g.headscale.example.com
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 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. See issue
1468 for more information.
Envoy
You need to add a new upgrade_type named tailscale-control-protocol. See
details.
Istio
Same as envoy, we can use EnvoyFilter to add a new upgrade_type named tailscale-control-protocol.
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-protocolNginx
The following basic Nginx configuration works with the Headscale configuration as shown
above. Substitute placeholders and adjust the configuration as needed:
<SERVER_NAME>: The server name for your instance, e.g.headscale.example.com- ``: Absolute path to your TLS certificate
- ``: Absolute path to your TLS private key
# 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.
- A Prometheus exporter for Headscale that provides network-level metrics using
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
- Ouroboros is designed for users to manage their own devices, rather than for
[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
- A simple headscale admin UI for Unraid, it offers Local (
[headscale-console](https://github.com/rickli-cloud/headscale-console)
- WebAssembly-based client supporting SSH, VNC and RDP with optional
self-service capabilities
- WebAssembly-based client supporting SSH, VNC and RDP with optional
[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
- A modern Headscale management panel with a clean, network-operations-focused
You can ask for support on our Discord server 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 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:
pacman -S headscaleFedora, 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.
Gentoo
emerge --ask net-vpn/headscaleGentoo specific documentation is available here.
OpenBSD
Headscale is available in ports. The port installs headscale as system service with rc.d and provides usage
instructions upon installation.
pkg_add headscaleSetup 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 or Podman is required. The container
image can be found on Docker Hub and GitHub Container
Registry. The container image URLs are:
- Docker Hub:
docker.io/headscale/headscale:<VERSION> - GitHub Container Registry:
ghcr.io/juanfont/headscale:<VERSION>
Configure and run headscale
Create a directory on the container host to store headscale's configuration and the SQLite database:
shellmkdir -p ./headscale/{config,lib} cd ./headscaleDownload 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 for details.Start headscale from within the previously created
./headscaledirectory:shelldocker 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> \ serveNote: use
0.0.0.0:8080:8080instead of127.0.0.1:8080:8080if 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"]Verify headscale is running:
Follow the container logs:
shelldocker logs --follow headscaleVerify running containers:
shelldocker psVerify headscale is available:
shellcurl http://127.0.0.1:8080/health
Continue on the getting started page 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:
docker run -it docker.io/headscale/headscale:x.x.x-debug shYou can also execute commands directly, such as ls /ko-app in this example:
docker run docker.io/headscale/headscale:x.x.x-debug ls /ko-appUsing 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
or the GitHub Container Registry package page.
For example, to run a specific development build:
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> \
serveSee Running headscale in a container for full container setup instructions.
Binaries
Pre-built binaries from the latest successful build on main are available
via nightly.link:
| OS | Arch | Download |
|---|---|---|
| Linux | amd64 | headscale-linux-amd64 |
| Linux | arm64 | headscale-linux-arm64 |
| macOS | amd64 | headscale-darwin-amd64 |
| macOS | arm64 | headscale-darwin-arm64 |
After downloading and extracting the archive, make the binary executable and follow the
standalone binary installation
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.
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.
Download the latest headscale package for your platform (
.debfor Ubuntu and Debian).shellHEADSCALE_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"Install headscale:
shellsudo apt install ./headscale.debConfigure headscale by editing the configuration file. An up-to date example
configuration file is also available in/usr/share/doc/headscale/examples/config-example.yaml:shellsudo nano /etc/headscale/config.yamlRestart headscale to pick up configuration changes:
shellsudo systemctl restart headscaleVerify that headscale is running as intended:
shellsudo systemctl status headscale
Continue on the getting started page 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. Headscale is run by a dedicated local user and the service itself is
managed by systemd.
Download the latest
headscalebinary from GitHub's release page:shellsudo wget --output-document=/usr/bin/headscale \ https://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>Make
headscaleexecutable:shellsudo chmod +x /usr/bin/headscaleAdd a dedicated local user to run headscale:
shellsudo useradd \ --create-home \ --home-dir /var/lib/headscale/ \ --system \ --user-group \ --shell /usr/sbin/nologin \ headscaleDownload 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 for details.shellsudo mkdir -p /etc/headscale sudo nano /etc/headscale/config.yamlCopy headscale's systemd service file
to/etc/systemd/system/headscale.serviceand adjust it to suit your local setup. The following parameters likely need
to be modified:ExecStart,WorkingDirectory,ReadWritePaths.In
/etc/headscale/config.yaml, override the defaultheadscaleunix socket with a path that is writable by theheadscaleuser or group:yaml title="config.yaml"unix_socket: /var/run/headscale/headscale.sockReload systemd to load the new configuration file:
shellsystemctl daemon-reloadEnable and start the new headscale service:
shellsystemctl enable --now headscaleVerify that headscale is running as intended:
shellsystemctl status headscale
Continue on the getting started page 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 and Buf
(Protobuf generator). See the Contributing section in the GitHub
README for more information.
OpenBSD
Install from source
# 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/sbinInstall from source via cross compile
# 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/sbinUsage 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 client with headscale.
Installation
Install the official Tailscale Android client from the Google Play Store or F-Droid.
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
- If needed, tap
Log inon 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 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.
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 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, selectAdd Account... - Enter the URL of your headscale instance (e.g
https://headscale.example.com) and pressAdd Account - Follow the login procedure in the browser
tvOS
Installation
Install the official Tailscale tvOS client from the App Store.
!!! 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 configurationand confirm the appearing popup by clicking theAllowbutton, but don't sign in yet - Open Settings (the Apple tvOS settings) > Apps > Tailscale
- Under
ALTERNATE COORDINATION SERVER URL, selectURL - Enter the URL of your headscale instance (e.g
https://headscale.example.com) and pressOK - 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 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 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.ghttps://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.
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=redactedThis typically means that the registry keys above was not set appropriately.
To reset and try again, it is important to do the following:
- Shut down the Tailscale service (or the client running in the tray)
- Delete Tailscale Application data folder, located at
C:\Users\\AppData\Local\Tailscaleand try to connect again. - Ensure the Windows node is deleted from headscale (to ensure fresh setup)
- 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