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.
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.
# Development versionwget-Oconfig.yamlhttps://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml
-# Version 0.23.0
-wget-Oconfig.yamlhttps://raw.githubusercontent.com/juanfont/headscale/v0.23.0/config-example.yaml
+# Version 0.24.0
+wget-Oconfig.yamlhttps://raw.githubusercontent.com/juanfont/headscale/v0.24.0/config-example.yaml
# Development versioncurl-oconfig.yamlhttps://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml
-# Version 0.23.0
-curl-oconfig.yamlhttps://raw.githubusercontent.com/juanfont/headscale/v0.23.0/config-example.yaml
+# Version 0.24.0
+curl-oconfig.yamlhttps://raw.githubusercontent.com/juanfont/headscale/v0.24.0/config-example.yaml
\ No newline at end of file
diff --git a/0.24.0/search/search_index.json b/0.24.0/search/search_index.json
index 26883be1..3b47de4c 100644
--- a/0.24.0/search/search_index.json
+++ b/0.24.0/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.
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.
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.
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.
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\")
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.
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.
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
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 related 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 continuously 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.
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
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.
Verify that DNS records are properly set using the DNS querying tool of your choice:
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:
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
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.
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":"
Download the headscale binary from GitHub's release page. Make sure to use the same version as on the server.
Put the binary somewhere in your PATH, e.g. /usr/local/bin/headscale
Make headscale executable:
chmod +x /usr/local/bin/headscale\n
Provide the connection parameters for the remote headscale server either via a minimal YAML configuration file or via environment variables:
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.
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.
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.
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.
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.
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.
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.
If you want to validate that certificate renewal completed successfully, this can be done either manually, or through external monitoring software. Two examples of doing this manually:
Open the URL for your headscale server in your browser of choice, and manually inspecting the expiry date of the certificate you receive.
"},{"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.
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.
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
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
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.
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.
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.
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.
"},{"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 environment 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.
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.
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.
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.
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.
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":"
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
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.
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.
Start the headscale server while working in the host headscale directory:
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
"},{"location":"setup/install/container/#register-machine-using-a-pre-authenticated-key","title":"Register machine using a pre authenticated key","text":"
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.
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.
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
Install headscale:
sudo apt install ./headscale.deb\n
Configure headscale by editing the configuration file:
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.
Download the latest headscale binary from GitHub's release page:
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.
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.
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
Reload systemd to load the new configuration file:
systemctl daemon-reload\n
Enable and start the new headscale service:
systemctl enable --now headscale\n
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 prerequisites\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 prerequisites\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
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.
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
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
"},{"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.
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:
"},{"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):
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):
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
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:
Shut down the Tailscale service (or the client running in the tray)
Delete Tailscale Application data folder, located at C:\\Users\\<USERNAME>\\AppData\\Local\\Tailscale and try to connect again.
Ensure the Windows node is deleted from headscale (to ensure fresh setup)
Start Tailscale on the Windows machine and retry the login.
"}]}
\ 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.
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.
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.
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.
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\")
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.
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.
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
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.24.0: https://github.com/juanfont/headscale/blob/v0.24.0/config-example.yaml
# Development version\nwget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml\n\n# Version 0.24.0\nwget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.24.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.24.0\ncurl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.24.0/config-example.yaml\n
"},{"location":"ref/dns/","title":"DNS","text":"
Headscale supports most DNS features from Tailscale. DNS related 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 continuously 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.
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
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.
Verify that DNS records are properly set using the DNS querying tool of your choice:
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:
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
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.
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":"
Download the headscale binary from GitHub's release page. Make sure to use the same version as on the server.
Put the binary somewhere in your PATH, e.g. /usr/local/bin/headscale
Make headscale executable:
chmod +x /usr/local/bin/headscale\n
Provide the connection parameters for the remote headscale server either via a minimal YAML configuration file or via environment variables:
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.
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.
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.
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.
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.
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.
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.
If you want to validate that certificate renewal completed successfully, this can be done either manually, or through external monitoring software. Two examples of doing this manually:
Open the URL for your headscale server in your browser of choice, and manually inspecting the expiry date of the certificate you receive.
"},{"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.
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.
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
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
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.
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.
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.
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.
"},{"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 environment 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.
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.
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.
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.
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.
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":"
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
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.
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.
Start the headscale server while working in the host headscale directory:
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
"},{"location":"setup/install/container/#register-machine-using-a-pre-authenticated-key","title":"Register machine using a pre authenticated key","text":"
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.
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.
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
Install headscale:
sudo apt install ./headscale.deb\n
Configure headscale by editing the configuration file:
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.
Download the latest headscale binary from GitHub's release page:
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.
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.
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
Reload systemd to load the new configuration file:
systemctl daemon-reload\n
Enable and start the new headscale service:
systemctl enable --now headscale\n
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 prerequisites\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 prerequisites\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
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.
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
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
"},{"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.
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:
"},{"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):
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):
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