From acea92d748223bfcd1dfaaf448c558ca693fd409 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 22 Dec 2024 17:38:35 +0000 Subject: [PATCH] Deployed b81420be to development with MkDocs 1.6.1 and mike 2.1.3 --- development/ref/oidc/index.html | 22 ++++++++--- development/search/search_index.json | 2 +- development/sitemap.xml | 56 +++++++++++++-------------- development/sitemap.xml.gz | Bin 432 -> 432 bytes 4 files changed, 46 insertions(+), 34 deletions(-) diff --git a/development/ref/oidc/index.html b/development/ref/oidc/index.html index 61d8ee55..b32c5f0e 100644 --- a/development/ref/oidc/index.html +++ b/development/ref/oidc/index.html @@ -31,11 +31,23 @@ allowed_users: - alice@example.com - # If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed. - # This will transform `first-name.last-name@example.com` to the user `first-name.last-name` - # If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following - # user: `first-name.last-name.example.com` - strip_email_domain: true + # Optional: PKCE (Proof Key for Code Exchange) configuration + # 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: + # Enable or disable PKCE support (default: false) + enabled: false + # PKCE method to use: + # - plain: Use plain code verifier + # - S256: Use SHA256 hashed code verifier (default, recommended) + method: S256 + + # If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed. + # This will transform `first-name.last-name@example.com` to the user `first-name.last-name` + # If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following + # user: `first-name.last-name.example.com` + strip_email_domain: true

Azure AD example

In order to integrate headscale with Azure Active Directory, we'll need to provision an App Registration with the correct scopes and redirect URI. Here with Terraform:

terraform.hcl
resource "azuread_application" "headscale" {
   display_name = "Headscale"
 
diff --git a/development/search/search_index.json b/development/search/search_index.json
index a05b979e..69348767 100644
--- a/development/search/search_index.json
+++ b/development/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-,:!=\\[\\]()\"`/]+|\\.(?!\\d)|&[lg]t;|(?!\\b)(?=[A-Z][a-z])","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Welcome to headscale","text":"

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.

"},{"location":"#design-goal","title":"Design goal","text":"

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 narrower scope, a single Tailnet, suitable for a personal use, or a small open-source organisation.

"},{"location":"#supporting-headscale","title":"Supporting headscale","text":"

Please see Sponsor for more information.

"},{"location":"#contributing","title":"Contributing","text":"

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.

"},{"location":"#about","title":"About","text":"

Headscale is maintained by Kristoffer Dalby and Juan Font.

"},{"location":"about/clients/","title":"Client and operating system support","text":"

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) 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)"},{"location":"about/contributing/","title":"Contributing","text":"

Headscale is \"Open Source, acknowledged contribution\", this means that any contribution will have to be discussed with the maintainers before being added to the project. This model has been chosen to reduce the risk of burnout by limiting the maintenance overhead of reviewing and validating third-party code.

"},{"location":"about/contributing/#why-do-we-have-this-model","title":"Why do we have this model?","text":"

Headscale has a small maintainer team that tries to balance working on the project, fixing bugs and reviewing contributions.

When we work on issues ourselves, we develop first hand knowledge of the code and it makes it possible for us to maintain and own the code as the project develops.

Code contributions are seen as a positive thing. People enjoy and engage with our project, but it also comes with some challenges; we have to understand the code, we have to understand the feature, we might have to become familiar with external libraries or services and we think about security implications. All those steps are required during the reviewing process. After the code has been merged, the feature has to be maintained. Any changes reliant on external services must be updated and expanded accordingly.

The review and day-1 maintenance adds a significant burden on the maintainers. Often we hope that the contributor will help out, but we found that most of the time, they disappear after their new feature was added.

This means that when someone contributes, we are mostly happy about it, but we do have to run it through a series of checks to establish if we actually can maintain this feature.

"},{"location":"about/contributing/#what-do-we-require","title":"What do we require?","text":"

A general description is provided here and an explicit list is provided in our pull request template.

All new features have to start out with a design document, which should be discussed on the issue tracker (not discord). It should include a use case for the feature, how it can be implemented, who will implement it and a plan for maintaining it.

All features have to be end-to-end tested (integration tests) and have good unit test coverage to ensure that they work as expected. This will also ensure that the feature continues to work as expected over time. If a change cannot be tested, a strong case for why this is not possible needs to be presented.

The contributor should help to maintain the feature over time. In case the feature is not maintained probably, the maintainers reserve themselves the right to remove features they redeem as unmaintainable. This should help to improve the quality of the software and keep it in a maintainable state.

"},{"location":"about/contributing/#bug-fixes","title":"Bug fixes","text":"

Headscale is open to code contributions for bug fixes without discussion.

"},{"location":"about/contributing/#documentation","title":"Documentation","text":"

If you find mistakes in the documentation, please submit a fix to the documentation.

"},{"location":"about/faq/","title":"Frequently Asked Questions","text":""},{"location":"about/faq/#what-is-the-design-goal-of-headscale","title":"What is the design goal of headscale?","text":"

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 Tailnet, suitable for a personal use, or a small open-source organisation.

"},{"location":"about/faq/#how-can-i-contribute","title":"How can I contribute?","text":"

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.

"},{"location":"about/faq/#why-is-acknowledged-contribution-the-chosen-model","title":"Why is 'acknowledged contribution' the chosen model?","text":"

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.

"},{"location":"about/faq/#whenwhy-is-feature-x-going-to-be-implemented","title":"When/Why is Feature X going to be implemented?","text":"

We don't know. We might be working on it. 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.
"},{"location":"about/faq/#do-you-support-y-method-of-deploying-headscale","title":"Do you support Y method of deploying headscale?","text":"

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 Docker 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.

"},{"location":"about/faq/#which-database-should-i-use","title":"Which database should I use?","text":"

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 usecases
  • 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.

"},{"location":"about/faq/#why-is-my-reverse-proxy-not-working-with-headscale","title":"Why is my reverse proxy not working with headscale?","text":"

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.

"},{"location":"about/faq/#can-i-use-headscale-and-tailscale-on-the-same-machine","title":"Can I use headscale and tailscale on the same machine?","text":"

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.

"},{"location":"about/features/","title":"Features","text":"

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
    • Interactive
    • Pre authenticated key
  • DNS
    • MagicDNS
    • Global and restricted nameservers (split DNS)
    • search domains
    • Extra DNS records (headscale only)
  • Taildrop (File Sharing)
  • Routing advertising (including exit nodes)
  • Dual stack (IPv4 and IPv6)
  • Ephemeral nodes
  • Embedded DERP server
  • Access control lists (GitHub label \"policy\")
    • ACL management via API
    • autogroup:internet
    • autogroup:self
    • autogroup:member
  • Node registration using Single-Sign-On (OpenID Connect) (GitHub label \"OIDC\")
    • Basic registration
    • Update user profile from identity provider
    • Dynamic ACL support
    • OIDC groups cannot be used in ACLs
  • Funnel (#1040)
  • Serve (#1234)
"},{"location":"about/help/","title":"Getting help","text":"

Join our Discord server for announcements and community support.

Please report bugs via GitHub issues

"},{"location":"about/releases/","title":"Releases","text":"

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.

An Atom/RSS feed of headscale releases is available here.

See the \"announcements\" channel on our Discord server for news about headscale.

"},{"location":"about/sponsor/","title":"Sponsor","text":"

If you like to support the development of headscale, please consider a donation via ko-fi.com/headscale. Thank you!

"},{"location":"ref/acls/","title":"ACLs","text":"

Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment.

For instance, instead of referring to users when defining groups you must use users (which are the equivalent to user/logins in Tailscale.com).

Please check https://tailscale.com/kb/1018/acls/ for further information.

When using ACL's the User borders are no longer applied. All machines whichever the User have the ability to communicate with other hosts as long as the ACL's permits this exchange.

"},{"location":"ref/acls/#acls-use-case-example","title":"ACLs use case example","text":"

Let's build an example use case for a small business (It may be the place where ACL's are the most useful).

We have a small company with a boss, an admin, two developers and an intern.

The boss should have access to all servers but not to the user's hosts. Admin should also have access to all hosts except that their permissions should be limited to maintaining the hosts (for example purposes). The developers can do anything they want on dev hosts but only watch on productions hosts. Intern can only interact with the development servers.

There's an additional server that acts as a router, connecting the VPN users to an internal network 10.20.0.0/16. Developers must have access to those internal resources.

Each user have at least a device connected to the network and we have some servers.

  • database.prod
  • database.dev
  • app-server1.prod
  • app-server1.dev
  • billing.internal
  • router.internal

"},{"location":"ref/acls/#acl-setup","title":"ACL setup","text":"

ACLs have to be written in huJSON.

When registering the servers we will need to add the flag --advertise-tags=tag:<tag1>,tag:<tag2>, and the user that is registering the server should be allowed to do it. Since anyone can add tags to a server they can register, the check of the tags is done on headscale server and only valid tags are applied. A tag is valid if the user that is registering it is allowed to do it.

To use ACLs in headscale, you must edit your config.yaml file. In there you will find a policy.path parameter. This will need to point to your ACL file. More info on how these policies are written can be found here.

Please reload or restart Headscale after updating the ACL file. Headscale may be reloaded either 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 ACL policy processing after each reload.

Here are the ACL's to implement the same permissions as above:

acl.json
{\n  // groups are collections of users having a common scope. A user can be in multiple groups\n  // groups cannot be composed of groups\n  \"groups\": {\n    \"group:boss\": [\"boss\"],\n    \"group:dev\": [\"dev1\", \"dev2\"],\n    \"group:admin\": [\"admin1\"],\n    \"group:intern\": [\"intern1\"]\n  },\n  // tagOwners in tailscale is an association between a TAG and the people allowed to set this TAG on a server.\n  // This is documented [here](https://tailscale.com/kb/1068/acl-tags#defining-a-tag)\n  // and explained [here](https://tailscale.com/blog/rbac-like-it-was-meant-to-be/)\n  \"tagOwners\": {\n    // the administrators can add servers in production\n    \"tag:prod-databases\": [\"group:admin\"],\n    \"tag:prod-app-servers\": [\"group:admin\"],\n\n    // the boss can tag any server as internal\n    \"tag:internal\": [\"group:boss\"],\n\n    // dev can add servers for dev purposes as well as admins\n    \"tag:dev-databases\": [\"group:admin\", \"group:dev\"],\n    \"tag:dev-app-servers\": [\"group:admin\", \"group:dev\"]\n\n    // interns cannot add servers\n  },\n  // hosts should be defined using its IP addresses and a subnet mask.\n  // to define a single host, use a /32 mask. You cannot use DNS entries here,\n  // as they're prone to be hijacked by replacing their IP addresses.\n  // see https://github.com/tailscale/tailscale/issues/3800 for more information.\n  \"hosts\": {\n    \"postgresql.internal\": \"10.20.0.2/32\",\n    \"webservers.internal\": \"10.20.10.1/29\"\n  },\n  \"acls\": [\n    // boss have access to all servers\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:boss\"],\n      \"dst\": [\n        \"tag:prod-databases:*\",\n        \"tag:prod-app-servers:*\",\n        \"tag:internal:*\",\n        \"tag:dev-databases:*\",\n        \"tag:dev-app-servers:*\"\n      ]\n    },\n\n    // admin have only access to administrative ports of the servers, in tcp/22\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:admin\"],\n      \"proto\": \"tcp\",\n      \"dst\": [\n        \"tag:prod-databases:22\",\n        \"tag:prod-app-servers:22\",\n        \"tag:internal:22\",\n        \"tag:dev-databases:22\",\n        \"tag:dev-app-servers:22\"\n      ]\n    },\n\n    // we also allow admin to ping the servers\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:admin\"],\n      \"proto\": \"icmp\",\n      \"dst\": [\n        \"tag:prod-databases:*\",\n        \"tag:prod-app-servers:*\",\n        \"tag:internal:*\",\n        \"tag:dev-databases:*\",\n        \"tag:dev-app-servers:*\"\n      ]\n    },\n\n    // developers have access to databases servers and application servers on all ports\n    // they can only view the applications servers in prod and have no access to databases servers in production\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:dev\"],\n      \"dst\": [\n        \"tag:dev-databases:*\",\n        \"tag:dev-app-servers:*\",\n        \"tag:prod-app-servers:80,443\"\n      ]\n    },\n    // developers have access to the internal network through the router.\n    // the internal network is composed of HTTPS endpoints and Postgresql\n    // database servers. There's an additional rule to allow traffic to be\n    // forwarded to the internal subnet, 10.20.0.0/16. See this issue\n    // https://github.com/juanfont/headscale/issues/502\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:dev\"],\n      \"dst\": [\"10.20.0.0/16:443,5432\", \"router.internal:0\"]\n    },\n\n    // servers should be able to talk to database in tcp/5432. Database should not be able to initiate connections to\n    // applications servers\n    {\n      \"action\": \"accept\",\n      \"src\": [\"tag:dev-app-servers\"],\n      \"proto\": \"tcp\",\n      \"dst\": [\"tag:dev-databases:5432\"]\n    },\n    {\n      \"action\": \"accept\",\n      \"src\": [\"tag:prod-app-servers\"],\n      \"dst\": [\"tag:prod-databases:5432\"]\n    },\n\n    // interns have access to dev-app-servers only in reading mode\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:intern\"],\n      \"dst\": [\"tag:dev-app-servers:80,443\"]\n    },\n\n    // We still have to allow internal users communications since nothing guarantees that each user have\n    // their own users.\n    { \"action\": \"accept\", \"src\": [\"boss\"], \"dst\": [\"boss:*\"] },\n    { \"action\": \"accept\", \"src\": [\"dev1\"], \"dst\": [\"dev1:*\"] },\n    { \"action\": \"accept\", \"src\": [\"dev2\"], \"dst\": [\"dev2:*\"] },\n    { \"action\": \"accept\", \"src\": [\"admin1\"], \"dst\": [\"admin1:*\"] },\n    { \"action\": \"accept\", \"src\": [\"intern1\"], \"dst\": [\"intern1:*\"] }\n  ]\n}\n
"},{"location":"ref/configuration/","title":"Configuration","text":"
  • Headscale loads its configuration from a YAML file
  • It searches for config.yaml in the following paths:
    • /etc/headscale
    • $HOME/.headscale
    • the current working directory
  • Use the command line flag -c, --config to load the configuration from a different path
  • Validate the configuration file with: headscale configtest

Get the example configuration from the GitHub repository

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.

View on GitHubDownload with wgetDownload with curl
  • Development version: https://github.com/juanfont/headscale/blob/main/config-example.yaml
  • Version 0.23.0: https://github.com/juanfont/headscale/blob/v0.23.0/config-example.yaml
# Development version\nwget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml\n\n# Version 0.23.0\nwget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.23.0/config-example.yaml\n
# Development version\ncurl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml\n\n# Version 0.23.0\ncurl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.23.0/config-example.yaml\n
"},{"location":"ref/dns/","title":"DNS","text":"

Headscale supports most DNS features from Tailscale. DNS releated settings can be configured within dns section of the configuration file.

"},{"location":"ref/dns/#setting-extra-dns-records","title":"Setting extra DNS records","text":"

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 continously watches for changes:

  • Use the dns.extra_records option 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 option dns.extra_records_path in 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\".

Limitations

Currently, only A and AAAA records are processed by Tailscale.

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

    Static entries, via dns.extra_recordsDynamic entries, via dns.extra_records_path config.yaml
    dns:\n  ...\n  extra_records:\n    - name: \"grafana.myvpn.example.com\"\n      type: \"A\"\n      value: \"100.64.0.3\"\n\n    - name: \"prometheus.myvpn.example.com\"\n      type: \"A\"\n      value: \"100.64.0.3\"\n  ...\n

    Restart your headscale instance.

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

    Headscale picks up changes to the above JSON file automatically.

    Good to know

    • The dns.extra_records_path option in the configuration file needs to reference the JSON file containing extra DNS records.
    • 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.
  2. Verify that DNS records are properly set using the DNS querying tool of your choice:

    Query with digQuery with drill
    dig +short grafana.myvpn.example.com\n100.64.0.3\n
    drill -Q grafana.myvpn.example.com\n100.64.0.3\n
  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.conf
    server {\n    listen 80;\n    listen [::]:80;\n\n    server_name grafana.myvpn.example.com;\n\n    location / {\n        proxy_pass http://localhost:3000;\n        proxy_set_header Host $http_host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n\n}\n
"},{"location":"ref/exit-node/","title":"Exit Nodes","text":""},{"location":"ref/exit-node/#on-the-node","title":"On the node","text":"

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

$ sudo tailscale up --login-server https://headscale.example.com --advertise-exit-node\n

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

$ sudo tailscale set --advertise-exit-node\n

To use a node as an exit node, IP forwarding must be enabled on the node. Check the official Tailscale documentation for how to enable IP forwarding.

"},{"location":"ref/exit-node/#on-the-control-server","title":"On the control server","text":"
$ # list nodes\n$ headscale routes list\nID | Node   | Prefix    | Advertised | Enabled | Primary\n1  |        | 0.0.0.0/0 | false      | false   | -\n2  |        | ::/0      | false      | false   | -\n3  | phobos | 0.0.0.0/0 | true       | false   | -\n4  | phobos | ::/0      | true       | false   | -\n\n$ # enable routes for phobos\n$ headscale routes enable -r 3\n$ headscale routes enable -r 4\n\n$ # Check node list again. The routes are now enabled.\n$ headscale routes list\nID | Node   | Prefix    | Advertised | Enabled | Primary\n1  |        | 0.0.0.0/0 | false      | false   | -\n2  |        | ::/0      | false      | false   | -\n3  | phobos | 0.0.0.0/0 | true       | true    | -\n4  | phobos | ::/0      | true       | true    | -\n
"},{"location":"ref/exit-node/#on-the-client","title":"On the client","text":"

The exit node can now be used with:

$ sudo tailscale set --exit-node phobos\n

Check the official Tailscale documentation for how to do it on your device.

"},{"location":"ref/oidc/","title":"Configuring headscale to use OIDC authentication","text":"

In order to authenticate users through a centralized solution one must enable the OIDC integration.

Known limitations:

  • No dynamic ACL support
  • OIDC groups cannot be used in ACLs
"},{"location":"ref/oidc/#basic-configuration","title":"Basic configuration","text":"

In your config.yaml, customize this to your liking:

config.yaml
oidc:\n  # Block further startup until the OIDC provider is healthy and available\n  only_start_if_oidc_is_available: true\n  # Specified by your OIDC provider\n  issuer: \"https://your-oidc.issuer.com/path\"\n  # Specified/generated by your OIDC provider\n  client_id: \"your-oidc-client-id\"\n  client_secret: \"your-oidc-client-secret\"\n  # alternatively, set `client_secret_path` to read the secret from the file.\n  # It resolves environment variables, making integration to systemd's\n  # `LoadCredential` straightforward:\n  #client_secret_path: \"${CREDENTIALS_DIRECTORY}/oidc_client_secret\"\n  # as third option, it's also possible to load the oidc secret from environment variables\n  # set HEADSCALE_OIDC_CLIENT_SECRET to the required value\n\n  # Customize the scopes used in the OIDC flow, defaults to \"openid\", \"profile\" and \"email\" and add custom query\n  # parameters to the Authorize Endpoint request. Scopes default to \"openid\", \"profile\" and \"email\".\n  scope: [\"openid\", \"profile\", \"email\", \"custom\"]\n  # Optional: Passed on to the browser login request \u2013 used to tweak behaviour for the OIDC provider\n  extra_params:\n    domain_hint: example.com\n\n  # Optional: List allowed principal domains and/or users. If an authenticated user's domain is not in this list,\n  # the authentication request will be rejected.\n  allowed_domains:\n    - example.com\n  # Optional. Note that groups from Keycloak have a leading '/'.\n  allowed_groups:\n    - /headscale\n  # Optional.\n  allowed_users:\n    - alice@example.com\n\n  # If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed.\n  # This will transform `first-name.last-name@example.com` to the user `first-name.last-name`\n  # If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following\n  # user: `first-name.last-name.example.com`\n  strip_email_domain: true\n
"},{"location":"ref/oidc/#azure-ad-example","title":"Azure AD example","text":"

In order to integrate headscale with Azure Active Directory, we'll need to provision an App Registration with the correct scopes and redirect URI. Here with Terraform:

terraform.hcl
resource \"azuread_application\" \"headscale\" {\n  display_name = \"Headscale\"\n\n  sign_in_audience = \"AzureADMyOrg\"\n  fallback_public_client_enabled = false\n\n  required_resource_access {\n    // Microsoft Graph\n    resource_app_id = \"00000003-0000-0000-c000-000000000000\"\n\n    resource_access {\n      // scope: profile\n      id   = \"14dad69e-099b-42c9-810b-d002981feec1\"\n      type = \"Scope\"\n    }\n    resource_access {\n      // scope: openid\n      id   = \"37f7f235-527c-4136-accd-4a02d197296e\"\n      type = \"Scope\"\n    }\n    resource_access {\n      // scope: email\n      id   = \"64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0\"\n      type = \"Scope\"\n    }\n  }\n  web {\n    # Points at your running headscale instance\n    redirect_uris = [\"https://headscale.example.com/oidc/callback\"]\n\n    implicit_grant {\n      access_token_issuance_enabled = false\n      id_token_issuance_enabled = true\n    }\n  }\n\n  group_membership_claims = [\"SecurityGroup\"]\n  optional_claims {\n    # Expose group memberships\n    id_token {\n      name = \"groups\"\n    }\n  }\n}\n\nresource \"azuread_application_password\" \"headscale-application-secret\" {\n  display_name          = \"Headscale Server\"\n  application_object_id = azuread_application.headscale.object_id\n}\n\nresource \"azuread_service_principal\" \"headscale\" {\n  application_id = azuread_application.headscale.application_id\n}\n\nresource \"azuread_service_principal_password\" \"headscale\" {\n  service_principal_id = azuread_service_principal.headscale.id\n  end_date_relative    = \"44640h\"\n}\n\noutput \"headscale_client_id\" {\n  value = azuread_application.headscale.application_id\n}\n\noutput \"headscale_client_secret\" {\n  value = azuread_application_password.headscale-application-secret.value\n}\n

And in your headscale config.yaml:

config.yaml
oidc:\n  issuer: \"https://login.microsoftonline.com/<tenant-UUID>/v2.0\"\n  client_id: \"<client-id-from-terraform>\"\n  client_secret: \"<client-secret-from-terraform>\"\n\n  # Optional: add \"groups\"\n  scope: [\"openid\", \"profile\", \"email\"]\n  extra_params:\n    # Use your own domain, associated with Azure AD\n    domain_hint: example.com\n    # Optional: Force the Azure AD account picker\n    prompt: select_account\n
"},{"location":"ref/oidc/#google-oauth-example","title":"Google OAuth Example","text":"

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.

"},{"location":"ref/oidc/#steps","title":"Steps","text":"
  1. Go to Google Console and login or create an account if you don't have one.
  2. Create a project (if you don't already have one).
  3. On the left hand menu, go to APIs and services -> Credentials
  4. Click Create Credentials -> OAuth client ID
  5. Under Application Type, choose Web Application
  6. For Name, enter whatever you like
  7. Under Authorised redirect URIs, use https://example.com/oidc/callback, replacing example.com with your headscale URL.
  8. Click Save at the bottom of the form
  9. Take note of the Client ID and Client secret, you can also download it for reference if you need it.
  10. Edit your headscale config, under oidc, filling in your client_id and client_secret: config.yaml
    oidc:\n  issuer: \"https://accounts.google.com\"\n  client_id: \"\"\n  client_secret: \"\"\n  scope: [\"openid\", \"profile\", \"email\"]\n

You can also use allowed_domains and allowed_users to restrict the users who can authenticate.

"},{"location":"ref/remote-cli/","title":"Controlling headscale with remote CLI","text":"

This documentation has the goal of showing a user how-to control a headscale instance from a remote machine with the headscale command line binary.

"},{"location":"ref/remote-cli/#prerequisite","title":"Prerequisite","text":"
  • A workstation to run headscale (any supported platform, e.g. Linux).
  • A headscale server with gRPC enabled.
  • Connections to the gRPC port (default: 50443) are allowed.
  • Remote access requires an encrypted connection via TLS.
  • An API key to authenticate with the headscale server.
"},{"location":"ref/remote-cli/#create-an-api-key","title":"Create an API key","text":"

We need to create an API key to authenticate with the remote headscale server when using it from our workstation.

To create an API key, log into your headscale server and generate a key:

headscale apikeys create --expiration 90d\n

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

To list the keys currently associated with the server:

headscale apikeys list\n

and to expire a key:

headscale apikeys expire --prefix \"<PREFIX>\"\n
"},{"location":"ref/remote-cli/#download-and-configure-headscale","title":"Download and configure headscale","text":"
  1. Download the headscale binary from GitHub's release page. Make sure to use the same version as on the server.

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

  3. Make headscale executable:

    chmod +x /usr/local/bin/headscale\n
  4. Provide the connection parameters for the remote headscale server either via a minimal YAML configuration file or via environment variables:

    Minimal YAML configuration fileEnvironment variables config.yaml
    cli:\n    address: <HEADSCALE_ADDRESS>:<PORT>\n    api_key: <API_KEY_FROM_PREVIOUS_STEP>\n
    export HEADSCALE_CLI_ADDRESS=\"<HEADSCALE_ADDRESS>:<PORT>\"\nexport HEADSCALE_CLI_API_KEY=\"<API_KEY_FROM_PREVIOUS_STEP>\"\n

    Bug

    Headscale currently requires at least an empty configuration file when environment variables are used to specify connection details. See issue 2193 for more information.

    This instructs the headscale binary to connect to a remote instance at <HEADSCALE_ADDRESS>:<PORT>, instead of connecting to the local instance.

  5. Test the connection

    Let us run the headscale command to verify that we can connect by listing our nodes:

    headscale nodes list\n

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

"},{"location":"ref/remote-cli/#behind-a-proxy","title":"Behind a proxy","text":"

It is possible to run the gRPC remote endpoint behind a reverse proxy, like Nginx, and have it run on the same port as headscale.

While this is not a supported feature, an example on how this can be set up on NixOS is shown here.

"},{"location":"ref/remote-cli/#troubleshooting","title":"Troubleshooting","text":"
  • Make sure you have the same headscale version on your server and workstation.
  • Ensure that connections to the gRPC port are allowed.
  • Verify that your TLS certificate is valid and trusted.
  • If you don't have access to a trusted certificate (e.g. from Let's Encrypt), either:
    • Add your self-signed certificate to the trust store of your OS or
    • Disable certificate verification by either setting cli.insecure: true in the configuration file or by setting HEADSCALE_CLI_INSECURE=1 via an environment variable. We do not recommend to disable certificate validation.
"},{"location":"ref/tls/","title":"Running the service via TLS (optional)","text":""},{"location":"ref/tls/#bring-your-own-certificate","title":"Bring your own certificate","text":"

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_cert_path configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.

config.yaml
tls_cert_path: \"\"\ntls_key_path: \"\"\n

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

"},{"location":"ref/tls/#lets-encrypt-acme","title":"Let's Encrypt / ACME","text":"

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.

config.yaml
tls_letsencrypt_hostname: \"\"\ntls_letsencrypt_listen: \":http\"\ntls_letsencrypt_cache_dir: \".cache\"\ntls_letsencrypt_challenge_type: HTTP-01\n
"},{"location":"ref/tls/#challenge-types","title":"Challenge types","text":"

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

"},{"location":"ref/tls/#http-01","title":"HTTP-01","text":"

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.

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.

"},{"location":"ref/tls/#tls-alpn-01","title":"TLS-ALPN-01","text":"

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.

"},{"location":"ref/tls/#technical-description","title":"Technical description","text":"

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.
"},{"location":"ref/tls/#checking-certificate-expiry","title":"Checking certificate expiry","text":"

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

  1. Open the URL for your headscale server in your browser of choice, and manually inspecting the expiry date of the certificate you receive.
  2. Or, check remotely from CLI using openssl:
$ openssl s_client -servername [hostname] -connect [hostname]:443 | openssl x509 -noout -dates\n(...)\nnotBefore=Feb  8 09:48:26 2024 GMT\nnotAfter=May  8 09:48:25 2024 GMT\n
"},{"location":"ref/tls/#log-output-from-the-autocert-library","title":"Log output from the autocert library","text":"

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

acme/autocert: missing server name\n

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

acme/autocert: host \"[foo]\" not configured in HostWhitelist\n

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

The source code for autocert can be found here

"},{"location":"ref/integration/reverse-proxy/","title":"Running headscale behind a reverse proxy","text":"

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.

"},{"location":"ref/integration/reverse-proxy/#websockets","title":"WebSockets","text":"

The reverse proxy MUST be configured to support WebSockets to communicate with Tailscale clients.

WebSockets support is also required when using the headscale embedded DERP server. In this case, you will also need to expose the UDP port used for STUN (by default, udp/3478). Please check our config-example.yaml.

"},{"location":"ref/integration/reverse-proxy/#cloudflare","title":"Cloudflare","text":"

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 this issue

"},{"location":"ref/integration/reverse-proxy/#tls","title":"TLS","text":"

Headscale can be configured not to use TLS, leaving it to the reverse proxy to handle. Add the following configuration values to your headscale config file.

config.yaml
server_url: https://<YOUR_SERVER_NAME> # This should be the FQDN at which headscale will be served\nlisten_addr: 0.0.0.0:8080\nmetrics_listen_addr: 0.0.0.0:9090\ntls_cert_path: \"\"\ntls_key_path: \"\"\n
"},{"location":"ref/integration/reverse-proxy/#nginx","title":"nginx","text":"

The following example configuration can be used in your nginx setup, substituting values as necessary. <IP:PORT> should be the IP address and port where headscale is running. In most cases, this will be http://localhost:8080.

nginx.conf
map $http_upgrade $connection_upgrade {\n    default      upgrade;\n    ''           close;\n}\n\nserver {\n    listen 80;\n    listen [::]:80;\n\n    listen 443      ssl http2;\n    listen [::]:443 ssl http2;\n\n    server_name <YOUR_SERVER_NAME>;\n\n    ssl_certificate <PATH_TO_CERT>;\n    ssl_certificate_key <PATH_CERT_KEY>;\n    ssl_protocols TLSv1.2 TLSv1.3;\n\n    location / {\n        proxy_pass http://<IP:PORT>;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection $connection_upgrade;\n        proxy_set_header Host $server_name;\n        proxy_redirect http:// https://;\n        proxy_buffering off;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        add_header Strict-Transport-Security \"max-age=15552000; includeSubDomains\" always;\n    }\n}\n
"},{"location":"ref/integration/reverse-proxy/#istioenvoy","title":"istio/envoy","text":"

If you using Istio ingressgateway or Envoy as reverse proxy, there are some tips for you. If not set, you may see some debug log in proxy as below:

Sending local reply with details upgrade_failed\n
"},{"location":"ref/integration/reverse-proxy/#envoy","title":"Envoy","text":"

You need to add a new upgrade_type named tailscale-control-protocol. see details

"},{"location":"ref/integration/reverse-proxy/#istio","title":"Istio","text":"

Same as envoy, we can use EnvoyFilter to add upgrade_type.

apiVersion: networking.istio.io/v1alpha3\nkind: EnvoyFilter\nmetadata:\n  name: headscale-behind-istio-ingress\n  namespace: istio-system\nspec:\n  configPatches:\n    - applyTo: NETWORK_FILTER\n      match:\n        listener:\n          filterChain:\n            filter:\n              name: envoy.filters.network.http_connection_manager\n      patch:\n        operation: MERGE\n        value:\n          typed_config:\n            \"@type\": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager\n            upgrade_configs:\n              - upgrade_type: tailscale-control-protocol\n
"},{"location":"ref/integration/reverse-proxy/#caddy","title":"Caddy","text":"

The following Caddyfile is all that is necessary to use Caddy as a reverse proxy for headscale, in combination with the config.yaml specifications above to disable headscale's built in TLS. Replace values as necessary - <YOUR_SERVER_NAME> should be the FQDN at which headscale will be served, and <IP:PORT> should be the IP address and port where headscale is running. In most cases, this will be localhost:8080.

Caddyfile
<YOUR_SERVER_NAME> {\n    reverse_proxy <IP:PORT>\n}\n

Caddy v2 will automatically provision a certificate for your domain/subdomain, force HTTPS, and proxy websockets - no further configuration is necessary.

For a slightly more complex configuration which utilizes Docker containers to manage Caddy, headscale, and Headscale-UI, Guru Computing's guide is an excellent reference.

"},{"location":"ref/integration/reverse-proxy/#apache","title":"Apache","text":"

The following minimal Apache config will proxy traffic to the headscale instance on <IP:PORT>. Note that upgrade=any is required as a parameter for ProxyPass so that WebSockets 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.

apache.conf
<VirtualHost *:443>\n    ServerName <YOUR_SERVER_NAME>\n\n    ProxyPreserveHost On\n    ProxyPass / http://<IP:PORT>/ upgrade=any\n\n    SSLEngine On\n    SSLCertificateFile <PATH_TO_CERT>\n    SSLCertificateKeyFile <PATH_CERT_KEY>\n</VirtualHost>\n
"},{"location":"ref/integration/tools/","title":"Tools related to headscale","text":"

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 and scripts related to headscale.

Name Repository Link Description tailscale-manager Github Dynamically manage Tailscale route advertisements headscalebacktosqlite Github Migrate headscale from PostgreSQL back to SQLite"},{"location":"ref/integration/web-ui/","title":"Web interfaces for headscale","text":"

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.

Name Repository Link Description headscale-webui Github A simple headscale web UI for small-scale deployments. headscale-ui Github A web frontend for the headscale Tailscale-compatible coordination server HeadscaleUi GitHub A static headscale admin ui, no backend enviroment required Headplane GitHub An advanced Tailscale inspired frontend for headscale headscale-admin Github Headscale-Admin is meant to be a simple, modern web interface for headscale ouroboros Github Ouroboros is designed for users to manage their own devices, rather than for admins

You can ask for support on our Discord server in the \"web-interfaces\" channel.

"},{"location":"setup/requirements/","title":"Requirements","text":"

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 4431.
  • A reasonably modern Linux or BSD based operating system.
  • A dedicated user account to run headscale.
  • A little bit of command line knowledge to configure and operate headscale.
"},{"location":"setup/requirements/#assumptions","title":"Assumptions","text":"

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

  • Headscale is running as system service via a dedicated 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, ACLs, SQLite database, \u2026) is located in /var/lib/headscale.
  • URLs and values that need to be replaced by the user are either denoted as <VALUE_TO_CHANGE> or use placeholder values such as headscale.example.com.

Please adjust to your local environment accordingly.

  1. The Tailscale client assumes HTTPS on port 443 in certain situations. Serving headscale either via HTTP or via HTTPS on a port other than 443 is possible but sticking with HTTPS on port 443 is strongly recommended for production setups. See issue 2164 for more information.\u00a0\u21a9

"},{"location":"setup/upgrade/","title":"Upgrade an existing installation","text":"

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.
  • Create a backup of your database.
  • Update headscale to the new version, preferably by following the same installation method.
  • Compare and update the configuration file.
  • Restart headscale.
"},{"location":"setup/install/community/","title":"Community packages","text":"

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 user account to run headscale
  • provide a default configuration
  • install headscale as system service

Community packages might be outdated

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

"},{"location":"setup/install/community/#arch-linux","title":"Arch Linux","text":"

Arch Linux offers a package for headscale, install via:

pacman -S headscale\n

The AUR package headscale-git can be used to build the current development version.

"},{"location":"setup/install/community/#fedora-rhel-centos","title":"Fedora, RHEL, CentOS","text":"

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.

"},{"location":"setup/install/community/#nix-nixos","title":"Nix, NixOS","text":"

A Nix package is available as: headscale. See the NixOS package site for installation details.

"},{"location":"setup/install/community/#gentoo","title":"Gentoo","text":"
emerge --ask net-vpn/headscale\n

Gentoo specific documentation is available here.

"},{"location":"setup/install/community/#openbsd","title":"OpenBSD","text":"

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

pkg_add headscale\n
"},{"location":"setup/install/container/","title":"Running headscale in a container","text":"

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.

This documentation has the goal of showing a user how-to set up and run headscale in a container. Docker is used as the reference container implementation, but there is no reason that it should not work with alternatives like Podman. The Docker image can be found on Docker Hub here.

"},{"location":"setup/install/container/#configure-and-run-headscale","title":"Configure and run headscale","text":"
  1. Prepare a directory on the host Docker node in your directory of choice, used to hold headscale configuration and the SQLite database:

    mkdir -p ./headscale/config\ncd ./headscale\n
  2. Download the example configuration for your chosen version and save it as: /etc/headscale/config.yaml. Adjust the configuration to suit your local environment. See Configuration for details.

    sudo mkdir -p /etc/headscale\nsudo nano /etc/headscale/config.yaml\n

    Alternatively, you can mount /var/lib and /var/run from your host system by adding --volume $(pwd)/lib:/var/lib/headscale and --volume $(pwd)/run:/var/run/headscale in the next step.

  3. Start the headscale server while working in the host headscale directory:

    docker run \\\n  --name headscale \\\n  --detach \\\n  --volume $(pwd)/config:/etc/headscale/ \\\n  --publish 127.0.0.1:8080:8080 \\\n  --publish 127.0.0.1:9090:9090 \\\n  headscale/headscale:<VERSION> \\\n  serve\n

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

    This command will mount config/ under /etc/headscale, forward port 8080 out of the container so the headscale instance becomes available and then detach so headscale runs in the background.

    Example docker-compose.yaml

    version: \"3.7\"\n\nservices:\n  headscale:\n    image: headscale/headscale:<VERSION>\n    restart: unless-stopped\n    container_name: headscale\n    ports:\n      - \"127.0.0.1:8080:8080\"\n      - \"127.0.0.1:9090:9090\"\n    volumes:\n      # Please change <CONFIG_PATH> to the fullpath of the config folder just created\n      - <CONFIG_PATH>:/etc/headscale\n    command: serve\n
  4. Verify headscale is running:

    Follow the container logs:

    docker logs --follow headscale\n

    Verify running containers:

    docker ps\n

    Verify headscale is available:

    curl http://127.0.0.1:9090/metrics\n
  5. Create a user (tailnet):

    docker exec -it headscale \\\n  headscale users create myfirstuser\n
"},{"location":"setup/install/container/#register-a-machine-normal-login","title":"Register a machine (normal login)","text":"

On a client machine, execute the tailscale login command:

tailscale up --login-server YOUR_HEADSCALE_URL\n

To register a machine when running headscale in a container, take the headscale command and pass it to the container:

docker exec -it headscale \\\n  headscale nodes register --user myfirstuser --key <YOUR_MACHINE_KEY>\n
"},{"location":"setup/install/container/#register-machine-using-a-pre-authenticated-key","title":"Register machine using a pre authenticated key","text":"

Generate a key using the command line:

docker exec -it headscale \\\n  headscale preauthkeys create --user myfirstuser --reusable --expiration 24h\n

This will return a pre-authenticated key that can be used to connect a node to headscale during the tailscale command:

tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\n
"},{"location":"setup/install/container/#debugging-headscale-running-in-docker","title":"Debugging headscale running in Docker","text":"

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

"},{"location":"setup/install/container/#running-the-debug-docker-container","title":"Running the debug Docker container","text":"

To run the debug Docker container, use the exact same commands as above, but replace headscale/headscale:x.x.x with 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.

"},{"location":"setup/install/container/#executing-commands-in-the-debug-container","title":"Executing commands in the debug container","text":"

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 headscale/headscale:x.x.x-debug sh\n

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

docker run headscale/headscale:x.x.x-debug ls /ko-app\n

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

"},{"location":"setup/install/official/","title":"Official releases","text":"

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.

"},{"location":"setup/install/official/#using-packages-for-debianubuntu-recommended","title":"Using packages for Debian/Ubuntu (recommended)","text":"

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

  1. Download the latest headscale package for your platform (.deb for Ubuntu and Debian).

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

    sudo apt install ./headscale.deb\n
  3. Configure headscale by editing the configuration file:

    sudo nano /etc/headscale/config.yaml\n
  4. Enable and start the headscale service:

    sudo systemctl enable --now headscale\n
  5. Verify that headscale is running as intended:

    sudo systemctl status headscale\n
"},{"location":"setup/install/official/#using-standalone-binaries-advanced","title":"Using standalone binaries (advanced)","text":"

Advanced

This installation method is considered advanced as one needs to take care of the headscale user and the systemd service themselves. If possible, use the DEB packages or a community package instead.

This section describes the installation of headscale according to the Requirements and assumptions. Headscale is run by a dedicated user and the service itself is managed by systemd.

  1. Download the latest headscale binary from GitHub's release page:

    sudo wget --output-document=/usr/local/bin/headscale \\\nhttps://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>\n
  2. Make headscale executable:

    sudo chmod +x /usr/local/bin/headscale\n
  3. Add a dedicated user to run headscale:

    sudo useradd \\\n --create-home \\\n --home-dir /var/lib/headscale/ \\\n --system \\\n --user-group \\\n --shell /usr/sbin/nologin \\\n headscale\n
  4. Download the example configuration for your chosen version and save it as: /etc/headscale/config.yaml. Adjust the configuration to suit your local environment. See Configuration for details.

    sudo mkdir -p /etc/headscale\nsudo nano /etc/headscale/config.yaml\n
  5. Copy headscale's systemd service file to /etc/systemd/system/headscale.service and adjust it to suit your local setup. The following parameters likely need to be modified: ExecStart, WorkingDirectory, ReadWritePaths.

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

    config.yaml
    unix_socket: /var/run/headscale/headscale.sock\n
  7. Reload systemd to load the new configuration file:

    systemctl daemon-reload\n
  8. Enable and start the new headscale service:

    systemctl enable --now headscale\n
  9. Verify that headscale is running as intended:

    systemctl status headscale\n
"},{"location":"setup/install/source/","title":"Build from source","text":"

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.

"},{"location":"setup/install/source/#openbsd","title":"OpenBSD","text":""},{"location":"setup/install/source/#install-from-source","title":"Install from source","text":"
# Install prerequistes\npkg_add go\n\ngit clone https://github.com/juanfont/headscale.git\n\ncd headscale\n\n# optionally checkout a release\n# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest\n# option b. get latest tag, this may be a beta release\nlatestTag=$(git describe --tags `git rev-list --tags --max-count=1`)\n\ngit checkout $latestTag\n\ngo build -ldflags=\"-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$latestTag\" github.com/juanfont/headscale\n\n# make it executable\nchmod a+x headscale\n\n# copy it to /usr/local/sbin\ncp headscale /usr/local/sbin\n
"},{"location":"setup/install/source/#install-from-source-via-cross-compile","title":"Install from source via cross compile","text":"
# Install prerequistes\n# 1. go v1.20+: headscale newer than 0.21 needs go 1.20+ to compile\n# 2. gmake: Makefile in the headscale repo is written in GNU make syntax\n\ngit clone https://github.com/juanfont/headscale.git\n\ncd headscale\n\n# optionally checkout a release\n# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest\n# option b. get latest tag, this may be a beta release\nlatestTag=$(git describe --tags `git rev-list --tags --max-count=1`)\n\ngit checkout $latestTag\n\nmake build GOOS=openbsd\n\n# copy headscale to openbsd machine and put it in /usr/local/sbin\n
"},{"location":"usage/getting-started/","title":"Getting started","text":"

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

Prerequisites

  • Headscale is installed and running as system service. Read the setup section for installation instructions.
  • The configuration file exists and is adjusted to suit your environment, see Configuration for details.
  • Headscale is reachable from the Internet. Verify this by opening client specific setup instructions in your browser, e.g. https://headscale.example.com/windows
  • The Tailscale client is installed, see Client and operating system support for more information.
"},{"location":"usage/getting-started/#getting-help","title":"Getting help","text":"

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

NativeContainer
# Show help\nheadscale help\n\n# Show help for a specific command\nheadscale <COMMAND> --help\n
# Show help\ndocker exec -it headscale \\\n  headscale help\n\n# Show help for a specific command\ndocker exec -it headscale \\\n  headscale <COMMAND> --help\n
"},{"location":"usage/getting-started/#manage-users","title":"Manage users","text":"

In headscale, a node (also known as machine or device) is always assigned to a specific user, a tailnet. Such users can be managed with the headscale users command. Invoke the built-in help for more information: headscale users --help.

"},{"location":"usage/getting-started/#create-a-user","title":"Create a user","text":"NativeContainer
headscale users create <USER>\n
docker exec -it headscale \\\n  headscale users create <USER>\n
"},{"location":"usage/getting-started/#list-existing-users","title":"List existing users","text":"NativeContainer
headscale users list\n
docker exec -it headscale \\\n  headscale users list\n
"},{"location":"usage/getting-started/#register-a-node","title":"Register a node","text":"

One has to register a node first to use headscale as coordination 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.

"},{"location":"usage/getting-started/#normal-interactive-login","title":"Normal, interactive login","text":"

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>\n

Usually, a browser window with further instructions is opened and contains the value for <YOUR_MACHINE_KEY>. Approve and register the node on your headscale server:

NativeContainer
headscale nodes register --user <USER> --key <YOUR_MACHINE_KEY>\n
docker exec -it headscale \\\n  headscale nodes register --user <USER> --key <YOUR_MACHINE_KEY>\n
"},{"location":"usage/getting-started/#using-a-preauthkey","title":"Using a preauthkey","text":"

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

NativeContainer
headscale preauthkeys create --user <USER>\n
docker exec -it headscale \\\n  headscale preauthkeys create --user <USER>\n

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

tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\n
"},{"location":"usage/connect/android/","title":"Connecting an Android client","text":"

This documentation has the goal of showing how a user can use the official Android Tailscale client with headscale.

"},{"location":"usage/connect/android/#installation","title":"Installation","text":"

Install the official Tailscale Android client from the Google Play Store or F-Droid.

"},{"location":"usage/connect/android/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"
  • 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
"},{"location":"usage/connect/apple/","title":"Connecting an Apple client","text":"

This documentation has the goal of showing how a user can use the official iOS and macOS Tailscale clients with headscale.

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.

"},{"location":"usage/connect/apple/#ios","title":"iOS","text":""},{"location":"usage/connect/apple/#installation","title":"Installation","text":"

Install the official Tailscale iOS client from the App Store.

"},{"location":"usage/connect/apple/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"
  • Open Tailscale and make sure you are not logged in to any account
  • Open Settings on the iOS device
  • Scroll down to the third party apps section, under Game Center or TV Provider
  • Find Tailscale and select it
  • If the iOS device was previously logged into Tailscale, switch the Reset Keychain toggle to on
  • Enter the URL of your headscale instance (e.g https://headscale.example.com) under Alternate Coordination Server URL
  • Restart the app by closing it from the iOS app switcher, open the app and select the regular sign in option (non-SSO). It should open up to the headscale authentication page.
  • Enter your credentials and log in. Headscale should now be working on your iOS device.
"},{"location":"usage/connect/apple/#macos","title":"macOS","text":""},{"location":"usage/connect/apple/#installation_1","title":"Installation","text":"

Choose one of the available Tailscale clients for macOS and install it.

"},{"location":"usage/connect/apple/#configuring-the-headscale-url_1","title":"Configuring the headscale URL","text":""},{"location":"usage/connect/apple/#command-line","title":"Command line","text":"

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

tailscale login --login-server <YOUR_HEADSCALE_URL>\n
"},{"location":"usage/connect/apple/#gui","title":"GUI","text":"
  • Option + Click the Tailscale icon in the menu and hover over the Debug menu
  • Under Custom Login Server, select Add Account...
  • Enter the URL of your headscale instance (e.g https://headscale.example.com) and press Add Account
  • Follow the login procedure in the browser
"},{"location":"usage/connect/apple/#tvos","title":"tvOS","text":""},{"location":"usage/connect/apple/#installation_2","title":"Installation","text":"

Install the official Tailscale tvOS client from the App Store.

Danger

Don't open the Tailscale App after installation!

"},{"location":"usage/connect/apple/#configuring-the-headscale-url_2","title":"Configuring the headscale URL","text":"
  • Open Settings (the Apple tvOS settings) > Apps > Tailscale
  • Under ALTERNATE COORDINATION SERVER URL, select URL
  • Enter the URL of your headscale instance (e.g https://headscale.example.com) and press OK
  • Return to the tvOS Home screen
  • Open Tailscale
  • Click the button Install VPN configuration and confirm the appearing popup by clicking the Allow button
  • Scan the QR code and follow the login procedure
"},{"location":"usage/connect/windows/","title":"Connecting a Windows client","text":"

This documentation has the goal of showing how a user can use the official Windows Tailscale client with headscale.

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.

"},{"location":"usage/connect/windows/#installation","title":"Installation","text":"

Download the Official Windows Client and install it.

"},{"location":"usage/connect/windows/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"

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

tailscale login --login-server <YOUR_HEADSCALE_URL>\n

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

"},{"location":"usage/connect/windows/#troubleshooting","title":"Troubleshooting","text":""},{"location":"usage/connect/windows/#unattended-mode","title":"Unattended mode","text":"

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

"},{"location":"usage/connect/windows/#failing-node-registration","title":"Failing node registration","text":"

If you are seeing repeated messages like:

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

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

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

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

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

  1. Shut down the Tailscale service (or the client running in the tray)
  2. Delete Tailscale Application data folder, located at C:\\Users\\<USERNAME>\\AppData\\Local\\Tailscale and try to connect again.
  3. Ensure the Windows node is deleted from headscale (to ensure fresh setup)
  4. Start Tailscale on the Windows machine and retry the login.
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-,:!=\\[\\]()\"`/]+|\\.(?!\\d)|&[lg]t;|(?!\\b)(?=[A-Z][a-z])","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Welcome to headscale","text":"

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.

"},{"location":"#design-goal","title":"Design goal","text":"

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 narrower scope, a single Tailnet, suitable for a personal use, or a small open-source organisation.

"},{"location":"#supporting-headscale","title":"Supporting headscale","text":"

Please see Sponsor for more information.

"},{"location":"#contributing","title":"Contributing","text":"

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.

"},{"location":"#about","title":"About","text":"

Headscale is maintained by Kristoffer Dalby and Juan Font.

"},{"location":"about/clients/","title":"Client and operating system support","text":"

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) 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)"},{"location":"about/contributing/","title":"Contributing","text":"

Headscale is \"Open Source, acknowledged contribution\", this means that any contribution will have to be discussed with the maintainers before being added to the project. This model has been chosen to reduce the risk of burnout by limiting the maintenance overhead of reviewing and validating third-party code.

"},{"location":"about/contributing/#why-do-we-have-this-model","title":"Why do we have this model?","text":"

Headscale has a small maintainer team that tries to balance working on the project, fixing bugs and reviewing contributions.

When we work on issues ourselves, we develop first hand knowledge of the code and it makes it possible for us to maintain and own the code as the project develops.

Code contributions are seen as a positive thing. People enjoy and engage with our project, but it also comes with some challenges; we have to understand the code, we have to understand the feature, we might have to become familiar with external libraries or services and we think about security implications. All those steps are required during the reviewing process. After the code has been merged, the feature has to be maintained. Any changes reliant on external services must be updated and expanded accordingly.

The review and day-1 maintenance adds a significant burden on the maintainers. Often we hope that the contributor will help out, but we found that most of the time, they disappear after their new feature was added.

This means that when someone contributes, we are mostly happy about it, but we do have to run it through a series of checks to establish if we actually can maintain this feature.

"},{"location":"about/contributing/#what-do-we-require","title":"What do we require?","text":"

A general description is provided here and an explicit list is provided in our pull request template.

All new features have to start out with a design document, which should be discussed on the issue tracker (not discord). It should include a use case for the feature, how it can be implemented, who will implement it and a plan for maintaining it.

All features have to be end-to-end tested (integration tests) and have good unit test coverage to ensure that they work as expected. This will also ensure that the feature continues to work as expected over time. If a change cannot be tested, a strong case for why this is not possible needs to be presented.

The contributor should help to maintain the feature over time. In case the feature is not maintained probably, the maintainers reserve themselves the right to remove features they redeem as unmaintainable. This should help to improve the quality of the software and keep it in a maintainable state.

"},{"location":"about/contributing/#bug-fixes","title":"Bug fixes","text":"

Headscale is open to code contributions for bug fixes without discussion.

"},{"location":"about/contributing/#documentation","title":"Documentation","text":"

If you find mistakes in the documentation, please submit a fix to the documentation.

"},{"location":"about/faq/","title":"Frequently Asked Questions","text":""},{"location":"about/faq/#what-is-the-design-goal-of-headscale","title":"What is the design goal of headscale?","text":"

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 Tailnet, suitable for a personal use, or a small open-source organisation.

"},{"location":"about/faq/#how-can-i-contribute","title":"How can I contribute?","text":"

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.

"},{"location":"about/faq/#why-is-acknowledged-contribution-the-chosen-model","title":"Why is 'acknowledged contribution' the chosen model?","text":"

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.

"},{"location":"about/faq/#whenwhy-is-feature-x-going-to-be-implemented","title":"When/Why is Feature X going to be implemented?","text":"

We don't know. We might be working on it. 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.
"},{"location":"about/faq/#do-you-support-y-method-of-deploying-headscale","title":"Do you support Y method of deploying headscale?","text":"

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 Docker 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.

"},{"location":"about/faq/#which-database-should-i-use","title":"Which database should I use?","text":"

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 usecases
  • 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.

"},{"location":"about/faq/#why-is-my-reverse-proxy-not-working-with-headscale","title":"Why is my reverse proxy not working with headscale?","text":"

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.

"},{"location":"about/faq/#can-i-use-headscale-and-tailscale-on-the-same-machine","title":"Can I use headscale and tailscale on the same machine?","text":"

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.

"},{"location":"about/features/","title":"Features","text":"

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
    • Interactive
    • Pre authenticated key
  • DNS
    • MagicDNS
    • Global and restricted nameservers (split DNS)
    • search domains
    • Extra DNS records (headscale only)
  • Taildrop (File Sharing)
  • Routing advertising (including exit nodes)
  • Dual stack (IPv4 and IPv6)
  • Ephemeral nodes
  • Embedded DERP server
  • Access control lists (GitHub label \"policy\")
    • ACL management via API
    • autogroup:internet
    • autogroup:self
    • autogroup:member
  • Node registration using Single-Sign-On (OpenID Connect) (GitHub label \"OIDC\")
    • Basic registration
    • Update user profile from identity provider
    • Dynamic ACL support
    • OIDC groups cannot be used in ACLs
  • Funnel (#1040)
  • Serve (#1234)
"},{"location":"about/help/","title":"Getting help","text":"

Join our Discord server for announcements and community support.

Please report bugs via GitHub issues

"},{"location":"about/releases/","title":"Releases","text":"

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.

An Atom/RSS feed of headscale releases is available here.

See the \"announcements\" channel on our Discord server for news about headscale.

"},{"location":"about/sponsor/","title":"Sponsor","text":"

If you like to support the development of headscale, please consider a donation via ko-fi.com/headscale. Thank you!

"},{"location":"ref/acls/","title":"ACLs","text":"

Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment.

For instance, instead of referring to users when defining groups you must use users (which are the equivalent to user/logins in Tailscale.com).

Please check https://tailscale.com/kb/1018/acls/ for further information.

When using ACL's the User borders are no longer applied. All machines whichever the User have the ability to communicate with other hosts as long as the ACL's permits this exchange.

"},{"location":"ref/acls/#acls-use-case-example","title":"ACLs use case example","text":"

Let's build an example use case for a small business (It may be the place where ACL's are the most useful).

We have a small company with a boss, an admin, two developers and an intern.

The boss should have access to all servers but not to the user's hosts. Admin should also have access to all hosts except that their permissions should be limited to maintaining the hosts (for example purposes). The developers can do anything they want on dev hosts but only watch on productions hosts. Intern can only interact with the development servers.

There's an additional server that acts as a router, connecting the VPN users to an internal network 10.20.0.0/16. Developers must have access to those internal resources.

Each user have at least a device connected to the network and we have some servers.

  • database.prod
  • database.dev
  • app-server1.prod
  • app-server1.dev
  • billing.internal
  • router.internal

"},{"location":"ref/acls/#acl-setup","title":"ACL setup","text":"

ACLs have to be written in huJSON.

When registering the servers we will need to add the flag --advertise-tags=tag:<tag1>,tag:<tag2>, and the user that is registering the server should be allowed to do it. Since anyone can add tags to a server they can register, the check of the tags is done on headscale server and only valid tags are applied. A tag is valid if the user that is registering it is allowed to do it.

To use ACLs in headscale, you must edit your config.yaml file. In there you will find a policy.path parameter. This will need to point to your ACL file. More info on how these policies are written can be found here.

Please reload or restart Headscale after updating the ACL file. Headscale may be reloaded either 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 ACL policy processing after each reload.

Here are the ACL's to implement the same permissions as above:

acl.json
{\n  // groups are collections of users having a common scope. A user can be in multiple groups\n  // groups cannot be composed of groups\n  \"groups\": {\n    \"group:boss\": [\"boss\"],\n    \"group:dev\": [\"dev1\", \"dev2\"],\n    \"group:admin\": [\"admin1\"],\n    \"group:intern\": [\"intern1\"]\n  },\n  // tagOwners in tailscale is an association between a TAG and the people allowed to set this TAG on a server.\n  // This is documented [here](https://tailscale.com/kb/1068/acl-tags#defining-a-tag)\n  // and explained [here](https://tailscale.com/blog/rbac-like-it-was-meant-to-be/)\n  \"tagOwners\": {\n    // the administrators can add servers in production\n    \"tag:prod-databases\": [\"group:admin\"],\n    \"tag:prod-app-servers\": [\"group:admin\"],\n\n    // the boss can tag any server as internal\n    \"tag:internal\": [\"group:boss\"],\n\n    // dev can add servers for dev purposes as well as admins\n    \"tag:dev-databases\": [\"group:admin\", \"group:dev\"],\n    \"tag:dev-app-servers\": [\"group:admin\", \"group:dev\"]\n\n    // interns cannot add servers\n  },\n  // hosts should be defined using its IP addresses and a subnet mask.\n  // to define a single host, use a /32 mask. You cannot use DNS entries here,\n  // as they're prone to be hijacked by replacing their IP addresses.\n  // see https://github.com/tailscale/tailscale/issues/3800 for more information.\n  \"hosts\": {\n    \"postgresql.internal\": \"10.20.0.2/32\",\n    \"webservers.internal\": \"10.20.10.1/29\"\n  },\n  \"acls\": [\n    // boss have access to all servers\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:boss\"],\n      \"dst\": [\n        \"tag:prod-databases:*\",\n        \"tag:prod-app-servers:*\",\n        \"tag:internal:*\",\n        \"tag:dev-databases:*\",\n        \"tag:dev-app-servers:*\"\n      ]\n    },\n\n    // admin have only access to administrative ports of the servers, in tcp/22\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:admin\"],\n      \"proto\": \"tcp\",\n      \"dst\": [\n        \"tag:prod-databases:22\",\n        \"tag:prod-app-servers:22\",\n        \"tag:internal:22\",\n        \"tag:dev-databases:22\",\n        \"tag:dev-app-servers:22\"\n      ]\n    },\n\n    // we also allow admin to ping the servers\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:admin\"],\n      \"proto\": \"icmp\",\n      \"dst\": [\n        \"tag:prod-databases:*\",\n        \"tag:prod-app-servers:*\",\n        \"tag:internal:*\",\n        \"tag:dev-databases:*\",\n        \"tag:dev-app-servers:*\"\n      ]\n    },\n\n    // developers have access to databases servers and application servers on all ports\n    // they can only view the applications servers in prod and have no access to databases servers in production\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:dev\"],\n      \"dst\": [\n        \"tag:dev-databases:*\",\n        \"tag:dev-app-servers:*\",\n        \"tag:prod-app-servers:80,443\"\n      ]\n    },\n    // developers have access to the internal network through the router.\n    // the internal network is composed of HTTPS endpoints and Postgresql\n    // database servers. There's an additional rule to allow traffic to be\n    // forwarded to the internal subnet, 10.20.0.0/16. See this issue\n    // https://github.com/juanfont/headscale/issues/502\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:dev\"],\n      \"dst\": [\"10.20.0.0/16:443,5432\", \"router.internal:0\"]\n    },\n\n    // servers should be able to talk to database in tcp/5432. Database should not be able to initiate connections to\n    // applications servers\n    {\n      \"action\": \"accept\",\n      \"src\": [\"tag:dev-app-servers\"],\n      \"proto\": \"tcp\",\n      \"dst\": [\"tag:dev-databases:5432\"]\n    },\n    {\n      \"action\": \"accept\",\n      \"src\": [\"tag:prod-app-servers\"],\n      \"dst\": [\"tag:prod-databases:5432\"]\n    },\n\n    // interns have access to dev-app-servers only in reading mode\n    {\n      \"action\": \"accept\",\n      \"src\": [\"group:intern\"],\n      \"dst\": [\"tag:dev-app-servers:80,443\"]\n    },\n\n    // We still have to allow internal users communications since nothing guarantees that each user have\n    // their own users.\n    { \"action\": \"accept\", \"src\": [\"boss\"], \"dst\": [\"boss:*\"] },\n    { \"action\": \"accept\", \"src\": [\"dev1\"], \"dst\": [\"dev1:*\"] },\n    { \"action\": \"accept\", \"src\": [\"dev2\"], \"dst\": [\"dev2:*\"] },\n    { \"action\": \"accept\", \"src\": [\"admin1\"], \"dst\": [\"admin1:*\"] },\n    { \"action\": \"accept\", \"src\": [\"intern1\"], \"dst\": [\"intern1:*\"] }\n  ]\n}\n
"},{"location":"ref/configuration/","title":"Configuration","text":"
  • Headscale loads its configuration from a YAML file
  • It searches for config.yaml in the following paths:
    • /etc/headscale
    • $HOME/.headscale
    • the current working directory
  • Use the command line flag -c, --config to load the configuration from a different path
  • Validate the configuration file with: headscale configtest

Get the example configuration from the GitHub repository

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.

View on GitHubDownload with wgetDownload with curl
  • Development version: https://github.com/juanfont/headscale/blob/main/config-example.yaml
  • Version 0.23.0: https://github.com/juanfont/headscale/blob/v0.23.0/config-example.yaml
# Development version\nwget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml\n\n# Version 0.23.0\nwget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.23.0/config-example.yaml\n
# Development version\ncurl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml\n\n# Version 0.23.0\ncurl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.23.0/config-example.yaml\n
"},{"location":"ref/dns/","title":"DNS","text":"

Headscale supports most DNS features from Tailscale. DNS releated settings can be configured within dns section of the configuration file.

"},{"location":"ref/dns/#setting-extra-dns-records","title":"Setting extra DNS records","text":"

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 continously watches for changes:

  • Use the dns.extra_records option 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 option dns.extra_records_path in 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\".

Limitations

Currently, only A and AAAA records are processed by Tailscale.

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

    Static entries, via dns.extra_recordsDynamic entries, via dns.extra_records_path config.yaml
    dns:\n  ...\n  extra_records:\n    - name: \"grafana.myvpn.example.com\"\n      type: \"A\"\n      value: \"100.64.0.3\"\n\n    - name: \"prometheus.myvpn.example.com\"\n      type: \"A\"\n      value: \"100.64.0.3\"\n  ...\n

    Restart your headscale instance.

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

    Headscale picks up changes to the above JSON file automatically.

    Good to know

    • The dns.extra_records_path option in the configuration file needs to reference the JSON file containing extra DNS records.
    • 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.
  2. Verify that DNS records are properly set using the DNS querying tool of your choice:

    Query with digQuery with drill
    dig +short grafana.myvpn.example.com\n100.64.0.3\n
    drill -Q grafana.myvpn.example.com\n100.64.0.3\n
  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.conf
    server {\n    listen 80;\n    listen [::]:80;\n\n    server_name grafana.myvpn.example.com;\n\n    location / {\n        proxy_pass http://localhost:3000;\n        proxy_set_header Host $http_host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n\n}\n
"},{"location":"ref/exit-node/","title":"Exit Nodes","text":""},{"location":"ref/exit-node/#on-the-node","title":"On the node","text":"

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

$ sudo tailscale up --login-server https://headscale.example.com --advertise-exit-node\n

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

$ sudo tailscale set --advertise-exit-node\n

To use a node as an exit node, IP forwarding must be enabled on the node. Check the official Tailscale documentation for how to enable IP forwarding.

"},{"location":"ref/exit-node/#on-the-control-server","title":"On the control server","text":"
$ # list nodes\n$ headscale routes list\nID | Node   | Prefix    | Advertised | Enabled | Primary\n1  |        | 0.0.0.0/0 | false      | false   | -\n2  |        | ::/0      | false      | false   | -\n3  | phobos | 0.0.0.0/0 | true       | false   | -\n4  | phobos | ::/0      | true       | false   | -\n\n$ # enable routes for phobos\n$ headscale routes enable -r 3\n$ headscale routes enable -r 4\n\n$ # Check node list again. The routes are now enabled.\n$ headscale routes list\nID | Node   | Prefix    | Advertised | Enabled | Primary\n1  |        | 0.0.0.0/0 | false      | false   | -\n2  |        | ::/0      | false      | false   | -\n3  | phobos | 0.0.0.0/0 | true       | true    | -\n4  | phobos | ::/0      | true       | true    | -\n
"},{"location":"ref/exit-node/#on-the-client","title":"On the client","text":"

The exit node can now be used with:

$ sudo tailscale set --exit-node phobos\n

Check the official Tailscale documentation for how to do it on your device.

"},{"location":"ref/oidc/","title":"Configuring headscale to use OIDC authentication","text":"

In order to authenticate users through a centralized solution one must enable the OIDC integration.

Known limitations:

  • No dynamic ACL support
  • OIDC groups cannot be used in ACLs
"},{"location":"ref/oidc/#basic-configuration","title":"Basic configuration","text":"

In your config.yaml, customize this to your liking:

config.yaml
oidc:\n  # Block further startup until the OIDC provider is healthy and available\n  only_start_if_oidc_is_available: true\n  # Specified by your OIDC provider\n  issuer: \"https://your-oidc.issuer.com/path\"\n  # Specified/generated by your OIDC provider\n  client_id: \"your-oidc-client-id\"\n  client_secret: \"your-oidc-client-secret\"\n  # alternatively, set `client_secret_path` to read the secret from the file.\n  # It resolves environment variables, making integration to systemd's\n  # `LoadCredential` straightforward:\n  #client_secret_path: \"${CREDENTIALS_DIRECTORY}/oidc_client_secret\"\n  # as third option, it's also possible to load the oidc secret from environment variables\n  # set HEADSCALE_OIDC_CLIENT_SECRET to the required value\n\n  # Customize the scopes used in the OIDC flow, defaults to \"openid\", \"profile\" and \"email\" and add custom query\n  # parameters to the Authorize Endpoint request. Scopes default to \"openid\", \"profile\" and \"email\".\n  scope: [\"openid\", \"profile\", \"email\", \"custom\"]\n  # Optional: Passed on to the browser login request \u2013 used to tweak behaviour for the OIDC provider\n  extra_params:\n    domain_hint: example.com\n\n  # Optional: List allowed principal domains and/or users. If an authenticated user's domain is not in this list,\n  # the authentication request will be rejected.\n  allowed_domains:\n    - example.com\n  # Optional. Note that groups from Keycloak have a leading '/'.\n  allowed_groups:\n    - /headscale\n  # Optional.\n  allowed_users:\n    - alice@example.com\n\n  # Optional: PKCE (Proof Key for Code Exchange) configuration\n  # PKCE adds an additional layer of security to the OAuth 2.0 authorization code flow\n  # by preventing authorization code interception attacks\n  # See https://datatracker.ietf.org/doc/html/rfc7636\n  pkce:\n    # Enable or disable PKCE support (default: false)\n    enabled: false\n    # PKCE method to use:\n    # - plain: Use plain code verifier\n    # - S256: Use SHA256 hashed code verifier (default, recommended)\n    method: S256\n\n  # If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed.\n  # This will transform `first-name.last-name@example.com` to the user `first-name.last-name`\n  # If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following\n  # user: `first-name.last-name.example.com`\n  strip_email_domain: true\n
"},{"location":"ref/oidc/#azure-ad-example","title":"Azure AD example","text":"

In order to integrate headscale with Azure Active Directory, we'll need to provision an App Registration with the correct scopes and redirect URI. Here with Terraform:

terraform.hcl
resource \"azuread_application\" \"headscale\" {\n  display_name = \"Headscale\"\n\n  sign_in_audience = \"AzureADMyOrg\"\n  fallback_public_client_enabled = false\n\n  required_resource_access {\n    // Microsoft Graph\n    resource_app_id = \"00000003-0000-0000-c000-000000000000\"\n\n    resource_access {\n      // scope: profile\n      id   = \"14dad69e-099b-42c9-810b-d002981feec1\"\n      type = \"Scope\"\n    }\n    resource_access {\n      // scope: openid\n      id   = \"37f7f235-527c-4136-accd-4a02d197296e\"\n      type = \"Scope\"\n    }\n    resource_access {\n      // scope: email\n      id   = \"64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0\"\n      type = \"Scope\"\n    }\n  }\n  web {\n    # Points at your running headscale instance\n    redirect_uris = [\"https://headscale.example.com/oidc/callback\"]\n\n    implicit_grant {\n      access_token_issuance_enabled = false\n      id_token_issuance_enabled = true\n    }\n  }\n\n  group_membership_claims = [\"SecurityGroup\"]\n  optional_claims {\n    # Expose group memberships\n    id_token {\n      name = \"groups\"\n    }\n  }\n}\n\nresource \"azuread_application_password\" \"headscale-application-secret\" {\n  display_name          = \"Headscale Server\"\n  application_object_id = azuread_application.headscale.object_id\n}\n\nresource \"azuread_service_principal\" \"headscale\" {\n  application_id = azuread_application.headscale.application_id\n}\n\nresource \"azuread_service_principal_password\" \"headscale\" {\n  service_principal_id = azuread_service_principal.headscale.id\n  end_date_relative    = \"44640h\"\n}\n\noutput \"headscale_client_id\" {\n  value = azuread_application.headscale.application_id\n}\n\noutput \"headscale_client_secret\" {\n  value = azuread_application_password.headscale-application-secret.value\n}\n

And in your headscale config.yaml:

config.yaml
oidc:\n  issuer: \"https://login.microsoftonline.com/<tenant-UUID>/v2.0\"\n  client_id: \"<client-id-from-terraform>\"\n  client_secret: \"<client-secret-from-terraform>\"\n\n  # Optional: add \"groups\"\n  scope: [\"openid\", \"profile\", \"email\"]\n  extra_params:\n    # Use your own domain, associated with Azure AD\n    domain_hint: example.com\n    # Optional: Force the Azure AD account picker\n    prompt: select_account\n
"},{"location":"ref/oidc/#google-oauth-example","title":"Google OAuth Example","text":"

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.

"},{"location":"ref/oidc/#steps","title":"Steps","text":"
  1. Go to Google Console and login or create an account if you don't have one.
  2. Create a project (if you don't already have one).
  3. On the left hand menu, go to APIs and services -> Credentials
  4. Click Create Credentials -> OAuth client ID
  5. Under Application Type, choose Web Application
  6. For Name, enter whatever you like
  7. Under Authorised redirect URIs, use https://example.com/oidc/callback, replacing example.com with your headscale URL.
  8. Click Save at the bottom of the form
  9. Take note of the Client ID and Client secret, you can also download it for reference if you need it.
  10. Edit your headscale config, under oidc, filling in your client_id and client_secret: config.yaml
    oidc:\n  issuer: \"https://accounts.google.com\"\n  client_id: \"\"\n  client_secret: \"\"\n  scope: [\"openid\", \"profile\", \"email\"]\n

You can also use allowed_domains and allowed_users to restrict the users who can authenticate.

"},{"location":"ref/remote-cli/","title":"Controlling headscale with remote CLI","text":"

This documentation has the goal of showing a user how-to control a headscale instance from a remote machine with the headscale command line binary.

"},{"location":"ref/remote-cli/#prerequisite","title":"Prerequisite","text":"
  • A workstation to run headscale (any supported platform, e.g. Linux).
  • A headscale server with gRPC enabled.
  • Connections to the gRPC port (default: 50443) are allowed.
  • Remote access requires an encrypted connection via TLS.
  • An API key to authenticate with the headscale server.
"},{"location":"ref/remote-cli/#create-an-api-key","title":"Create an API key","text":"

We need to create an API key to authenticate with the remote headscale server when using it from our workstation.

To create an API key, log into your headscale server and generate a key:

headscale apikeys create --expiration 90d\n

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

To list the keys currently associated with the server:

headscale apikeys list\n

and to expire a key:

headscale apikeys expire --prefix \"<PREFIX>\"\n
"},{"location":"ref/remote-cli/#download-and-configure-headscale","title":"Download and configure headscale","text":"
  1. Download the headscale binary from GitHub's release page. Make sure to use the same version as on the server.

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

  3. Make headscale executable:

    chmod +x /usr/local/bin/headscale\n
  4. Provide the connection parameters for the remote headscale server either via a minimal YAML configuration file or via environment variables:

    Minimal YAML configuration fileEnvironment variables config.yaml
    cli:\n    address: <HEADSCALE_ADDRESS>:<PORT>\n    api_key: <API_KEY_FROM_PREVIOUS_STEP>\n
    export HEADSCALE_CLI_ADDRESS=\"<HEADSCALE_ADDRESS>:<PORT>\"\nexport HEADSCALE_CLI_API_KEY=\"<API_KEY_FROM_PREVIOUS_STEP>\"\n

    Bug

    Headscale currently requires at least an empty configuration file when environment variables are used to specify connection details. See issue 2193 for more information.

    This instructs the headscale binary to connect to a remote instance at <HEADSCALE_ADDRESS>:<PORT>, instead of connecting to the local instance.

  5. Test the connection

    Let us run the headscale command to verify that we can connect by listing our nodes:

    headscale nodes list\n

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

"},{"location":"ref/remote-cli/#behind-a-proxy","title":"Behind a proxy","text":"

It is possible to run the gRPC remote endpoint behind a reverse proxy, like Nginx, and have it run on the same port as headscale.

While this is not a supported feature, an example on how this can be set up on NixOS is shown here.

"},{"location":"ref/remote-cli/#troubleshooting","title":"Troubleshooting","text":"
  • Make sure you have the same headscale version on your server and workstation.
  • Ensure that connections to the gRPC port are allowed.
  • Verify that your TLS certificate is valid and trusted.
  • If you don't have access to a trusted certificate (e.g. from Let's Encrypt), either:
    • Add your self-signed certificate to the trust store of your OS or
    • Disable certificate verification by either setting cli.insecure: true in the configuration file or by setting HEADSCALE_CLI_INSECURE=1 via an environment variable. We do not recommend to disable certificate validation.
"},{"location":"ref/tls/","title":"Running the service via TLS (optional)","text":""},{"location":"ref/tls/#bring-your-own-certificate","title":"Bring your own certificate","text":"

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_cert_path configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.

config.yaml
tls_cert_path: \"\"\ntls_key_path: \"\"\n

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

"},{"location":"ref/tls/#lets-encrypt-acme","title":"Let's Encrypt / ACME","text":"

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.

config.yaml
tls_letsencrypt_hostname: \"\"\ntls_letsencrypt_listen: \":http\"\ntls_letsencrypt_cache_dir: \".cache\"\ntls_letsencrypt_challenge_type: HTTP-01\n
"},{"location":"ref/tls/#challenge-types","title":"Challenge types","text":"

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

"},{"location":"ref/tls/#http-01","title":"HTTP-01","text":"

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.

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.

"},{"location":"ref/tls/#tls-alpn-01","title":"TLS-ALPN-01","text":"

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.

"},{"location":"ref/tls/#technical-description","title":"Technical description","text":"

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.
"},{"location":"ref/tls/#checking-certificate-expiry","title":"Checking certificate expiry","text":"

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

  1. Open the URL for your headscale server in your browser of choice, and manually inspecting the expiry date of the certificate you receive.
  2. Or, check remotely from CLI using openssl:
$ openssl s_client -servername [hostname] -connect [hostname]:443 | openssl x509 -noout -dates\n(...)\nnotBefore=Feb  8 09:48:26 2024 GMT\nnotAfter=May  8 09:48:25 2024 GMT\n
"},{"location":"ref/tls/#log-output-from-the-autocert-library","title":"Log output from the autocert library","text":"

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

acme/autocert: missing server name\n

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

acme/autocert: host \"[foo]\" not configured in HostWhitelist\n

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

The source code for autocert can be found here

"},{"location":"ref/integration/reverse-proxy/","title":"Running headscale behind a reverse proxy","text":"

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.

"},{"location":"ref/integration/reverse-proxy/#websockets","title":"WebSockets","text":"

The reverse proxy MUST be configured to support WebSockets to communicate with Tailscale clients.

WebSockets support is also required when using the headscale embedded DERP server. In this case, you will also need to expose the UDP port used for STUN (by default, udp/3478). Please check our config-example.yaml.

"},{"location":"ref/integration/reverse-proxy/#cloudflare","title":"Cloudflare","text":"

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 this issue

"},{"location":"ref/integration/reverse-proxy/#tls","title":"TLS","text":"

Headscale can be configured not to use TLS, leaving it to the reverse proxy to handle. Add the following configuration values to your headscale config file.

config.yaml
server_url: https://<YOUR_SERVER_NAME> # This should be the FQDN at which headscale will be served\nlisten_addr: 0.0.0.0:8080\nmetrics_listen_addr: 0.0.0.0:9090\ntls_cert_path: \"\"\ntls_key_path: \"\"\n
"},{"location":"ref/integration/reverse-proxy/#nginx","title":"nginx","text":"

The following example configuration can be used in your nginx setup, substituting values as necessary. <IP:PORT> should be the IP address and port where headscale is running. In most cases, this will be http://localhost:8080.

nginx.conf
map $http_upgrade $connection_upgrade {\n    default      upgrade;\n    ''           close;\n}\n\nserver {\n    listen 80;\n    listen [::]:80;\n\n    listen 443      ssl http2;\n    listen [::]:443 ssl http2;\n\n    server_name <YOUR_SERVER_NAME>;\n\n    ssl_certificate <PATH_TO_CERT>;\n    ssl_certificate_key <PATH_CERT_KEY>;\n    ssl_protocols TLSv1.2 TLSv1.3;\n\n    location / {\n        proxy_pass http://<IP:PORT>;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection $connection_upgrade;\n        proxy_set_header Host $server_name;\n        proxy_redirect http:// https://;\n        proxy_buffering off;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        add_header Strict-Transport-Security \"max-age=15552000; includeSubDomains\" always;\n    }\n}\n
"},{"location":"ref/integration/reverse-proxy/#istioenvoy","title":"istio/envoy","text":"

If you using Istio ingressgateway or Envoy as reverse proxy, there are some tips for you. If not set, you may see some debug log in proxy as below:

Sending local reply with details upgrade_failed\n
"},{"location":"ref/integration/reverse-proxy/#envoy","title":"Envoy","text":"

You need to add a new upgrade_type named tailscale-control-protocol. see details

"},{"location":"ref/integration/reverse-proxy/#istio","title":"Istio","text":"

Same as envoy, we can use EnvoyFilter to add upgrade_type.

apiVersion: networking.istio.io/v1alpha3\nkind: EnvoyFilter\nmetadata:\n  name: headscale-behind-istio-ingress\n  namespace: istio-system\nspec:\n  configPatches:\n    - applyTo: NETWORK_FILTER\n      match:\n        listener:\n          filterChain:\n            filter:\n              name: envoy.filters.network.http_connection_manager\n      patch:\n        operation: MERGE\n        value:\n          typed_config:\n            \"@type\": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager\n            upgrade_configs:\n              - upgrade_type: tailscale-control-protocol\n
"},{"location":"ref/integration/reverse-proxy/#caddy","title":"Caddy","text":"

The following Caddyfile is all that is necessary to use Caddy as a reverse proxy for headscale, in combination with the config.yaml specifications above to disable headscale's built in TLS. Replace values as necessary - <YOUR_SERVER_NAME> should be the FQDN at which headscale will be served, and <IP:PORT> should be the IP address and port where headscale is running. In most cases, this will be localhost:8080.

Caddyfile
<YOUR_SERVER_NAME> {\n    reverse_proxy <IP:PORT>\n}\n

Caddy v2 will automatically provision a certificate for your domain/subdomain, force HTTPS, and proxy websockets - no further configuration is necessary.

For a slightly more complex configuration which utilizes Docker containers to manage Caddy, headscale, and Headscale-UI, Guru Computing's guide is an excellent reference.

"},{"location":"ref/integration/reverse-proxy/#apache","title":"Apache","text":"

The following minimal Apache config will proxy traffic to the headscale instance on <IP:PORT>. Note that upgrade=any is required as a parameter for ProxyPass so that WebSockets 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.

apache.conf
<VirtualHost *:443>\n    ServerName <YOUR_SERVER_NAME>\n\n    ProxyPreserveHost On\n    ProxyPass / http://<IP:PORT>/ upgrade=any\n\n    SSLEngine On\n    SSLCertificateFile <PATH_TO_CERT>\n    SSLCertificateKeyFile <PATH_CERT_KEY>\n</VirtualHost>\n
"},{"location":"ref/integration/tools/","title":"Tools related to headscale","text":"

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 and scripts related to headscale.

Name Repository Link Description tailscale-manager Github Dynamically manage Tailscale route advertisements headscalebacktosqlite Github Migrate headscale from PostgreSQL back to SQLite"},{"location":"ref/integration/web-ui/","title":"Web interfaces for headscale","text":"

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.

Name Repository Link Description headscale-webui Github A simple headscale web UI for small-scale deployments. headscale-ui Github A web frontend for the headscale Tailscale-compatible coordination server HeadscaleUi GitHub A static headscale admin ui, no backend enviroment required Headplane GitHub An advanced Tailscale inspired frontend for headscale headscale-admin Github Headscale-Admin is meant to be a simple, modern web interface for headscale ouroboros Github Ouroboros is designed for users to manage their own devices, rather than for admins

You can ask for support on our Discord server in the \"web-interfaces\" channel.

"},{"location":"setup/requirements/","title":"Requirements","text":"

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 4431.
  • A reasonably modern Linux or BSD based operating system.
  • A dedicated user account to run headscale.
  • A little bit of command line knowledge to configure and operate headscale.
"},{"location":"setup/requirements/#assumptions","title":"Assumptions","text":"

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

  • Headscale is running as system service via a dedicated 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, ACLs, SQLite database, \u2026) is located in /var/lib/headscale.
  • URLs and values that need to be replaced by the user are either denoted as <VALUE_TO_CHANGE> or use placeholder values such as headscale.example.com.

Please adjust to your local environment accordingly.

  1. The Tailscale client assumes HTTPS on port 443 in certain situations. Serving headscale either via HTTP or via HTTPS on a port other than 443 is possible but sticking with HTTPS on port 443 is strongly recommended for production setups. See issue 2164 for more information.\u00a0\u21a9

"},{"location":"setup/upgrade/","title":"Upgrade an existing installation","text":"

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.
  • Create a backup of your database.
  • Update headscale to the new version, preferably by following the same installation method.
  • Compare and update the configuration file.
  • Restart headscale.
"},{"location":"setup/install/community/","title":"Community packages","text":"

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 user account to run headscale
  • provide a default configuration
  • install headscale as system service

Community packages might be outdated

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

"},{"location":"setup/install/community/#arch-linux","title":"Arch Linux","text":"

Arch Linux offers a package for headscale, install via:

pacman -S headscale\n

The AUR package headscale-git can be used to build the current development version.

"},{"location":"setup/install/community/#fedora-rhel-centos","title":"Fedora, RHEL, CentOS","text":"

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.

"},{"location":"setup/install/community/#nix-nixos","title":"Nix, NixOS","text":"

A Nix package is available as: headscale. See the NixOS package site for installation details.

"},{"location":"setup/install/community/#gentoo","title":"Gentoo","text":"
emerge --ask net-vpn/headscale\n

Gentoo specific documentation is available here.

"},{"location":"setup/install/community/#openbsd","title":"OpenBSD","text":"

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

pkg_add headscale\n
"},{"location":"setup/install/container/","title":"Running headscale in a container","text":"

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.

This documentation has the goal of showing a user how-to set up and run headscale in a container. Docker is used as the reference container implementation, but there is no reason that it should not work with alternatives like Podman. The Docker image can be found on Docker Hub here.

"},{"location":"setup/install/container/#configure-and-run-headscale","title":"Configure and run headscale","text":"
  1. Prepare a directory on the host Docker node in your directory of choice, used to hold headscale configuration and the SQLite database:

    mkdir -p ./headscale/config\ncd ./headscale\n
  2. Download the example configuration for your chosen version and save it as: /etc/headscale/config.yaml. Adjust the configuration to suit your local environment. See Configuration for details.

    sudo mkdir -p /etc/headscale\nsudo nano /etc/headscale/config.yaml\n

    Alternatively, you can mount /var/lib and /var/run from your host system by adding --volume $(pwd)/lib:/var/lib/headscale and --volume $(pwd)/run:/var/run/headscale in the next step.

  3. Start the headscale server while working in the host headscale directory:

    docker run \\\n  --name headscale \\\n  --detach \\\n  --volume $(pwd)/config:/etc/headscale/ \\\n  --publish 127.0.0.1:8080:8080 \\\n  --publish 127.0.0.1:9090:9090 \\\n  headscale/headscale:<VERSION> \\\n  serve\n

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

    This command will mount config/ under /etc/headscale, forward port 8080 out of the container so the headscale instance becomes available and then detach so headscale runs in the background.

    Example docker-compose.yaml

    version: \"3.7\"\n\nservices:\n  headscale:\n    image: headscale/headscale:<VERSION>\n    restart: unless-stopped\n    container_name: headscale\n    ports:\n      - \"127.0.0.1:8080:8080\"\n      - \"127.0.0.1:9090:9090\"\n    volumes:\n      # Please change <CONFIG_PATH> to the fullpath of the config folder just created\n      - <CONFIG_PATH>:/etc/headscale\n    command: serve\n
  4. Verify headscale is running:

    Follow the container logs:

    docker logs --follow headscale\n

    Verify running containers:

    docker ps\n

    Verify headscale is available:

    curl http://127.0.0.1:9090/metrics\n
  5. Create a user (tailnet):

    docker exec -it headscale \\\n  headscale users create myfirstuser\n
"},{"location":"setup/install/container/#register-a-machine-normal-login","title":"Register a machine (normal login)","text":"

On a client machine, execute the tailscale login command:

tailscale up --login-server YOUR_HEADSCALE_URL\n

To register a machine when running headscale in a container, take the headscale command and pass it to the container:

docker exec -it headscale \\\n  headscale nodes register --user myfirstuser --key <YOUR_MACHINE_KEY>\n
"},{"location":"setup/install/container/#register-machine-using-a-pre-authenticated-key","title":"Register machine using a pre authenticated key","text":"

Generate a key using the command line:

docker exec -it headscale \\\n  headscale preauthkeys create --user myfirstuser --reusable --expiration 24h\n

This will return a pre-authenticated key that can be used to connect a node to headscale during the tailscale command:

tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\n
"},{"location":"setup/install/container/#debugging-headscale-running-in-docker","title":"Debugging headscale running in Docker","text":"

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

"},{"location":"setup/install/container/#running-the-debug-docker-container","title":"Running the debug Docker container","text":"

To run the debug Docker container, use the exact same commands as above, but replace headscale/headscale:x.x.x with 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.

"},{"location":"setup/install/container/#executing-commands-in-the-debug-container","title":"Executing commands in the debug container","text":"

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 headscale/headscale:x.x.x-debug sh\n

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

docker run headscale/headscale:x.x.x-debug ls /ko-app\n

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

"},{"location":"setup/install/official/","title":"Official releases","text":"

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.

"},{"location":"setup/install/official/#using-packages-for-debianubuntu-recommended","title":"Using packages for Debian/Ubuntu (recommended)","text":"

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

  1. Download the latest headscale package for your platform (.deb for Ubuntu and Debian).

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

    sudo apt install ./headscale.deb\n
  3. Configure headscale by editing the configuration file:

    sudo nano /etc/headscale/config.yaml\n
  4. Enable and start the headscale service:

    sudo systemctl enable --now headscale\n
  5. Verify that headscale is running as intended:

    sudo systemctl status headscale\n
"},{"location":"setup/install/official/#using-standalone-binaries-advanced","title":"Using standalone binaries (advanced)","text":"

Advanced

This installation method is considered advanced as one needs to take care of the headscale user and the systemd service themselves. If possible, use the DEB packages or a community package instead.

This section describes the installation of headscale according to the Requirements and assumptions. Headscale is run by a dedicated user and the service itself is managed by systemd.

  1. Download the latest headscale binary from GitHub's release page:

    sudo wget --output-document=/usr/local/bin/headscale \\\nhttps://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>\n
  2. Make headscale executable:

    sudo chmod +x /usr/local/bin/headscale\n
  3. Add a dedicated user to run headscale:

    sudo useradd \\\n --create-home \\\n --home-dir /var/lib/headscale/ \\\n --system \\\n --user-group \\\n --shell /usr/sbin/nologin \\\n headscale\n
  4. Download the example configuration for your chosen version and save it as: /etc/headscale/config.yaml. Adjust the configuration to suit your local environment. See Configuration for details.

    sudo mkdir -p /etc/headscale\nsudo nano /etc/headscale/config.yaml\n
  5. Copy headscale's systemd service file to /etc/systemd/system/headscale.service and adjust it to suit your local setup. The following parameters likely need to be modified: ExecStart, WorkingDirectory, ReadWritePaths.

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

    config.yaml
    unix_socket: /var/run/headscale/headscale.sock\n
  7. Reload systemd to load the new configuration file:

    systemctl daemon-reload\n
  8. Enable and start the new headscale service:

    systemctl enable --now headscale\n
  9. Verify that headscale is running as intended:

    systemctl status headscale\n
"},{"location":"setup/install/source/","title":"Build from source","text":"

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.

"},{"location":"setup/install/source/#openbsd","title":"OpenBSD","text":""},{"location":"setup/install/source/#install-from-source","title":"Install from source","text":"
# Install prerequistes\npkg_add go\n\ngit clone https://github.com/juanfont/headscale.git\n\ncd headscale\n\n# optionally checkout a release\n# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest\n# option b. get latest tag, this may be a beta release\nlatestTag=$(git describe --tags `git rev-list --tags --max-count=1`)\n\ngit checkout $latestTag\n\ngo build -ldflags=\"-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$latestTag\" github.com/juanfont/headscale\n\n# make it executable\nchmod a+x headscale\n\n# copy it to /usr/local/sbin\ncp headscale /usr/local/sbin\n
"},{"location":"setup/install/source/#install-from-source-via-cross-compile","title":"Install from source via cross compile","text":"
# Install prerequistes\n# 1. go v1.20+: headscale newer than 0.21 needs go 1.20+ to compile\n# 2. gmake: Makefile in the headscale repo is written in GNU make syntax\n\ngit clone https://github.com/juanfont/headscale.git\n\ncd headscale\n\n# optionally checkout a release\n# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest\n# option b. get latest tag, this may be a beta release\nlatestTag=$(git describe --tags `git rev-list --tags --max-count=1`)\n\ngit checkout $latestTag\n\nmake build GOOS=openbsd\n\n# copy headscale to openbsd machine and put it in /usr/local/sbin\n
"},{"location":"usage/getting-started/","title":"Getting started","text":"

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

Prerequisites

  • Headscale is installed and running as system service. Read the setup section for installation instructions.
  • The configuration file exists and is adjusted to suit your environment, see Configuration for details.
  • Headscale is reachable from the Internet. Verify this by opening client specific setup instructions in your browser, e.g. https://headscale.example.com/windows
  • The Tailscale client is installed, see Client and operating system support for more information.
"},{"location":"usage/getting-started/#getting-help","title":"Getting help","text":"

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

NativeContainer
# Show help\nheadscale help\n\n# Show help for a specific command\nheadscale <COMMAND> --help\n
# Show help\ndocker exec -it headscale \\\n  headscale help\n\n# Show help for a specific command\ndocker exec -it headscale \\\n  headscale <COMMAND> --help\n
"},{"location":"usage/getting-started/#manage-users","title":"Manage users","text":"

In headscale, a node (also known as machine or device) is always assigned to a specific user, a tailnet. Such users can be managed with the headscale users command. Invoke the built-in help for more information: headscale users --help.

"},{"location":"usage/getting-started/#create-a-user","title":"Create a user","text":"NativeContainer
headscale users create <USER>\n
docker exec -it headscale \\\n  headscale users create <USER>\n
"},{"location":"usage/getting-started/#list-existing-users","title":"List existing users","text":"NativeContainer
headscale users list\n
docker exec -it headscale \\\n  headscale users list\n
"},{"location":"usage/getting-started/#register-a-node","title":"Register a node","text":"

One has to register a node first to use headscale as coordination 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.

"},{"location":"usage/getting-started/#normal-interactive-login","title":"Normal, interactive login","text":"

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>\n

Usually, a browser window with further instructions is opened and contains the value for <YOUR_MACHINE_KEY>. Approve and register the node on your headscale server:

NativeContainer
headscale nodes register --user <USER> --key <YOUR_MACHINE_KEY>\n
docker exec -it headscale \\\n  headscale nodes register --user <USER> --key <YOUR_MACHINE_KEY>\n
"},{"location":"usage/getting-started/#using-a-preauthkey","title":"Using a preauthkey","text":"

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

NativeContainer
headscale preauthkeys create --user <USER>\n
docker exec -it headscale \\\n  headscale preauthkeys create --user <USER>\n

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

tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\n
"},{"location":"usage/connect/android/","title":"Connecting an Android client","text":"

This documentation has the goal of showing how a user can use the official Android Tailscale client with headscale.

"},{"location":"usage/connect/android/#installation","title":"Installation","text":"

Install the official Tailscale Android client from the Google Play Store or F-Droid.

"},{"location":"usage/connect/android/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"
  • 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
"},{"location":"usage/connect/apple/","title":"Connecting an Apple client","text":"

This documentation has the goal of showing how a user can use the official iOS and macOS Tailscale clients with headscale.

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.

"},{"location":"usage/connect/apple/#ios","title":"iOS","text":""},{"location":"usage/connect/apple/#installation","title":"Installation","text":"

Install the official Tailscale iOS client from the App Store.

"},{"location":"usage/connect/apple/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"
  • Open Tailscale and make sure you are not logged in to any account
  • Open Settings on the iOS device
  • Scroll down to the third party apps section, under Game Center or TV Provider
  • Find Tailscale and select it
  • If the iOS device was previously logged into Tailscale, switch the Reset Keychain toggle to on
  • Enter the URL of your headscale instance (e.g https://headscale.example.com) under Alternate Coordination Server URL
  • Restart the app by closing it from the iOS app switcher, open the app and select the regular sign in option (non-SSO). It should open up to the headscale authentication page.
  • Enter your credentials and log in. Headscale should now be working on your iOS device.
"},{"location":"usage/connect/apple/#macos","title":"macOS","text":""},{"location":"usage/connect/apple/#installation_1","title":"Installation","text":"

Choose one of the available Tailscale clients for macOS and install it.

"},{"location":"usage/connect/apple/#configuring-the-headscale-url_1","title":"Configuring the headscale URL","text":""},{"location":"usage/connect/apple/#command-line","title":"Command line","text":"

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

tailscale login --login-server <YOUR_HEADSCALE_URL>\n
"},{"location":"usage/connect/apple/#gui","title":"GUI","text":"
  • Option + Click the Tailscale icon in the menu and hover over the Debug menu
  • Under Custom Login Server, select Add Account...
  • Enter the URL of your headscale instance (e.g https://headscale.example.com) and press Add Account
  • Follow the login procedure in the browser
"},{"location":"usage/connect/apple/#tvos","title":"tvOS","text":""},{"location":"usage/connect/apple/#installation_2","title":"Installation","text":"

Install the official Tailscale tvOS client from the App Store.

Danger

Don't open the Tailscale App after installation!

"},{"location":"usage/connect/apple/#configuring-the-headscale-url_2","title":"Configuring the headscale URL","text":"
  • Open Settings (the Apple tvOS settings) > Apps > Tailscale
  • Under ALTERNATE COORDINATION SERVER URL, select URL
  • Enter the URL of your headscale instance (e.g https://headscale.example.com) and press OK
  • Return to the tvOS Home screen
  • Open Tailscale
  • Click the button Install VPN configuration and confirm the appearing popup by clicking the Allow button
  • Scan the QR code and follow the login procedure
"},{"location":"usage/connect/windows/","title":"Connecting a Windows client","text":"

This documentation has the goal of showing how a user can use the official Windows Tailscale client with headscale.

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.

"},{"location":"usage/connect/windows/#installation","title":"Installation","text":"

Download the Official Windows Client and install it.

"},{"location":"usage/connect/windows/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"

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

tailscale login --login-server <YOUR_HEADSCALE_URL>\n

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

"},{"location":"usage/connect/windows/#troubleshooting","title":"Troubleshooting","text":""},{"location":"usage/connect/windows/#unattended-mode","title":"Unattended mode","text":"

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

"},{"location":"usage/connect/windows/#failing-node-registration","title":"Failing node registration","text":"

If you are seeing repeated messages like:

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

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

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

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

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

  1. Shut down the Tailscale service (or the client running in the tray)
  2. Delete Tailscale Application data folder, located at C:\\Users\\<USERNAME>\\AppData\\Local\\Tailscale and try to connect again.
  3. Ensure the Windows node is deleted from headscale (to ensure fresh setup)
  4. Start Tailscale on the Windows machine and retry the login.
"}]} \ No newline at end of file diff --git a/development/sitemap.xml b/development/sitemap.xml index 512e210f..0735fb69 100644 --- a/development/sitemap.xml +++ b/development/sitemap.xml @@ -2,114 +2,114 @@ https://juanfont.github.io/headscale/development/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/about/clients/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/about/contributing/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/about/faq/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/about/features/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/about/help/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/about/releases/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/about/sponsor/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/ref/acls/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/ref/configuration/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/ref/dns/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/ref/exit-node/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/ref/oidc/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/ref/remote-cli/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/ref/tls/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/ref/integration/reverse-proxy/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/ref/integration/tools/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/ref/integration/web-ui/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/setup/requirements/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/setup/upgrade/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/setup/install/community/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/setup/install/container/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/setup/install/official/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/setup/install/source/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/usage/getting-started/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/usage/connect/android/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/usage/connect/apple/ - 2024-12-17 + 2024-12-22 https://juanfont.github.io/headscale/development/usage/connect/windows/ - 2024-12-17 + 2024-12-22 \ No newline at end of file diff --git a/development/sitemap.xml.gz b/development/sitemap.xml.gz index 5b235e104294294fc9daa10ab6d908d30decf3e0..99b652d8aec7f04488100e78df259efb5bb63298 100644 GIT binary patch delta 415 zcmV;Q0bu^H1F!=JABzYGfL3Rb2OWQ@vBNeb?yXO-J%A$7js}@jMXFBUesmKIyKe{T zVo9K=4;24=dbzFn@D7y|;kkPrj@);0^mJ-9Gsr=Ok9((s|-Jg5^3xU5-6BlyQ1>50K z(V8wz^wG>f&Hl{}3wUW2?7Y|&axnu_A%kZ_*5N|PR1NS{n4J%qji6|t&uMAZi)L#h zOcAYE2X0AvqzFcMD8*m|rNVzo0=*V4BS2y%rVZW~(s{RUC8 J=-YM>002=}$ru0t delta 415 zcmV;Q0bu^H1F!=JABzYG0KZ_72OWP&P+&hu+}oaDeE>zG9S$<7N>rV`{pcnbcHa)v z#gaf#A1MC$^m1F$?j1@+rgL`~_T3IBa73EV-RsXE{nzegd^)v~D#%WSsh+#5NdE5J zx~@Y-fyL)KaG6~VS6x*1*mV2hTQ@%KbZiRlv(Ir#9L7ycRW-kwr-@018RdW4E(5Z= zf{!)$1TMlmB+d(vxRcwC{jJ4USvZcz{qea!9Q(r;6?yF4uXTU!{VxRm!cAOAsEcZc zM@0)>n&_jMftviA9TxD?3fOtEE2L}&ra%H;4OypVrpm&^UVC#gS&xLOv2Kj{9q}^7y z^v!~6X^+~n;=MIgmp9U+-Jx1mYnjMRcOIlFJ|(Rw%hCwt!yp-nM}lIGjHd|$`eY8Q zax1}tkG1M&a5KpEPtq$~5r}5E)UybJwwn}-R%v@&IVUhjwIW4c{~-^fety(p$KTx? JEjxA)0093P$ov2R