DNS¶
Headscale supports most DNS features from Tailscale. DNS releated settings can be configured within dns
section of the configuration file.
Setting extra DNS records¶
Headscale allows to set extra DNS records which are made available via MagicDNS. Extra DNS records can be configured either via static entries in the configuration file or from a JSON file that Headscale continously watches for changes:
- Use the
dns.extra_records
option in the configuration file for entries that are static and don't change while Headscale is running. Those entries are processed when Headscale is starting up and changes to the configuration require a restart of Headscale. - For dynamic DNS records that may be added, updated or removed while Headscale is running or DNS records that are generated by scripts the option
dns.extra_records_path
in the configuration file is useful. Set it to the absolute path of the JSON file containing DNS records and Headscale processes this file as it detects changes.
An example use case is to serve multiple apps on the same host via a reverse proxy like NGINX, in this case a Prometheus monitoring stack. This allows to nicely access the service with "http://grafana.myvpn.example.com" instead of the hostname and port combination "http://hostname-in-magic-dns.myvpn.example.com:3000".
Limitations
Currently, only A and AAAA records are processed by Tailscale.
-
Configure extra DNS records using one of the available configuration options:
config.yamldns: +
DNS - Headscale HeadscaleDNSDNS¶
Headscale supports most DNS features from Tailscale. DNS related settings can be configured within
dns
section of the configuration file.Setting extra DNS records¶
Headscale allows to set extra DNS records which are made available via MagicDNS. Extra DNS records can be configured either via static entries in the configuration file or from a JSON file that Headscale continuously watches for changes:
- Use the
dns.extra_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:
config.yamldns: ... extra_records: - name: "grafana.myvpn.example.com" diff --git a/development/ref/integration/web-ui/index.html b/development/ref/integration/web-ui/index.html index df8df565..da765f4c 100644 --- a/development/ref/integration/web-ui/index.html +++ b/development/ref/integration/web-ui/index.html @@ -1 +1 @@ -
Web UI - Headscale HeadscaleWeb UIWeb interfaces for headscale¶
Community contributions
This page contains community contributions. The projects listed here are not maintained by the headscale authors and are written by community members.
Headscale doesn't provide a built-in web interface but users may pick one from the available options.
Name Repository Link Description headscale-webui Github A simple headscale web UI for small-scale deployments. headscale-ui Github A web frontend for the headscale Tailscale-compatible coordination server HeadscaleUi GitHub A static headscale admin ui, no backend enviroment required Headplane GitHub An advanced Tailscale inspired frontend for headscale headscale-admin Github Headscale-Admin is meant to be a simple, modern web interface for headscale ouroboros Github Ouroboros is designed for users to manage their own devices, rather than for admins You can ask for support on our Discord server in the "web-interfaces" channel.
Web UI - Headscale HeadscaleWeb UIWeb interfaces for headscale¶
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.
Headscale is an open source, self-hosted implementation of the Tailscale control server.
This page contains the documentation for the latest version of headscale. Please also check our FAQ.
Join our Discord server for a chat and community support.
"},{"location":"#design-goal","title":"Design goal","text":"Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. It implements a narrower scope, a single Tailnet, suitable for a personal use, or a small open-source organisation.
"},{"location":"#supporting-headscale","title":"Supporting headscale","text":"Please see Sponsor for more information.
"},{"location":"#contributing","title":"Contributing","text":"Headscale is \"Open Source, acknowledged contribution\", this means that any contribution will have to be discussed with the Maintainers before being submitted.
Please see Contributing for more information.
"},{"location":"#about","title":"About","text":"Headscale is maintained by Kristoffer Dalby and Juan Font.
"},{"location":"about/clients/","title":"Client and operating system support","text":"We aim to support the last 10 releases of the Tailscale client on all provided operating systems and platforms. Some platforms might require additional configuration to connect with headscale.
OS Supports headscale Linux Yes OpenBSD Yes FreeBSD Yes Windows Yes (see docs and/windows
on your headscale for more information) Android Yes (see docs) macOS Yes (see docs and/apple
on your headscale for more information) iOS Yes (see docs and/apple
on your headscale for more information) tvOS Yes (see docs and/apple
on your headscale for more information)"},{"location":"about/contributing/","title":"Contributing","text":"Headscale is \"Open Source, acknowledged contribution\", this means that any contribution will have to be discussed with the maintainers before being added to the project. This model has been chosen to reduce the risk of burnout by limiting the maintenance overhead of reviewing and validating third-party code.
"},{"location":"about/contributing/#why-do-we-have-this-model","title":"Why do we have this model?","text":"Headscale has a small maintainer team that tries to balance working on the project, fixing bugs and reviewing contributions.
When we work on issues ourselves, we develop first hand knowledge of the code and it makes it possible for us to maintain and own the code as the project develops.
Code contributions are seen as a positive thing. People enjoy and engage with our project, but it also comes with some challenges; we have to understand the code, we have to understand the feature, we might have to become familiar with external libraries or services and we think about security implications. All those steps are required during the reviewing process. After the code has been merged, the feature has to be maintained. Any changes reliant on external services must be updated and expanded accordingly.
The review and day-1 maintenance adds a significant burden on the maintainers. Often we hope that the contributor will help out, but we found that most of the time, they disappear after their new feature was added.
This means that when someone contributes, we are mostly happy about it, but we do have to run it through a series of checks to establish if we actually can maintain this feature.
"},{"location":"about/contributing/#what-do-we-require","title":"What do we require?","text":"A general description is provided here and an explicit list is provided in our pull request template.
All new features have to start out with a design document, which should be discussed on the issue tracker (not discord). It should include a use case for the feature, how it can be implemented, who will implement it and a plan for maintaining it.
All features have to be end-to-end tested (integration tests) and have good unit test coverage to ensure that they work as expected. This will also ensure that the feature continues to work as expected over time. If a change cannot be tested, a strong case for why this is not possible needs to be presented.
The contributor should help to maintain the feature over time. In case the feature is not maintained probably, the maintainers reserve themselves the right to remove features they redeem as unmaintainable. This should help to improve the quality of the software and keep it in a maintainable state.
"},{"location":"about/contributing/#bug-fixes","title":"Bug fixes","text":"Headscale is open to code contributions for bug fixes without discussion.
"},{"location":"about/contributing/#documentation","title":"Documentation","text":"If you find mistakes in the documentation, please submit a fix to the documentation.
"},{"location":"about/faq/","title":"Frequently Asked Questions","text":""},{"location":"about/faq/#what-is-the-design-goal-of-headscale","title":"What is the design goal of headscale?","text":"Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. It implements a narrow scope, a single Tailnet, suitable for a personal use, or a small open-source organisation.
"},{"location":"about/faq/#how-can-i-contribute","title":"How can I contribute?","text":"Headscale is \"Open Source, acknowledged contribution\", this means that any contribution will have to be discussed with the Maintainers before being submitted.
Please see Contributing for more information.
"},{"location":"about/faq/#why-is-acknowledged-contribution-the-chosen-model","title":"Why is 'acknowledged contribution' the chosen model?","text":"Both maintainers have full-time jobs and families, and we want to avoid burnout. We also want to avoid frustration from contributors when their PRs are not accepted.
We are more than happy to exchange emails, or to have dedicated calls before a PR is submitted.
"},{"location":"about/faq/#whenwhy-is-feature-x-going-to-be-implemented","title":"When/Why is Feature X going to be implemented?","text":"We don't know. We might be working on it. If you're interested in contributing, please post a feature request about it.
Please be aware that there are a number of reasons why we might not accept specific contributions:
- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
- Given that we are reverse-engineering Tailscale to satisfy our own curiosity, we might be interested in implementing the feature ourselves.
- You are not sending unit and integration tests with it.
We currently support deploying headscale using our binaries and the DEB packages. Visit our installation guide using official releases for more information.
In addition to that, you may use packages provided by the community or from distributions. Learn more in the installation guide using community packages.
For convenience, we also build Docker images with headscale. But please be aware that we don't officially support deploying headscale using Docker. On our Discord server we have a \"docker-issues\" channel where you can ask for Docker-specific help to the community.
"},{"location":"about/faq/#which-database-should-i-use","title":"Which database should I use?","text":"We recommend the use of SQLite as database for headscale:
- SQLite is simple to setup and easy to use
- It scales well for all of headscale's usecases
- Development and testing happens primarily on SQLite
- PostgreSQL is still supported, but is considered to be in \"maintenance mode\"
The headscale project itself does not provide a tool to migrate from PostgreSQL to SQLite. Please have a look at the related tools documentation for migration tooling provided by the community.
"},{"location":"about/faq/#why-is-my-reverse-proxy-not-working-with-headscale","title":"Why is my reverse proxy not working with headscale?","text":"We don't know. We don't use reverse proxies with headscale ourselves, so we don't have any experience with them. We have community documentation on how to configure various reverse proxies, and a dedicated \"reverse-proxy-issues\" channel on our Discord server where you can ask for help to the community.
"},{"location":"about/faq/#can-i-use-headscale-and-tailscale-on-the-same-machine","title":"Can I use headscale and tailscale on the same machine?","text":"Running headscale on a machine that is also in the tailnet can cause problems with subnet routers, traffic relay nodes, and MagicDNS. It might work, but it is not supported.
"},{"location":"about/features/","title":"Features","text":"Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. This page provides on overview of headscale's feature and compatibility with the Tailscale control server:
- Full \"base\" support of Tailscale's features
- Node registration
- Interactive
- Pre authenticated key
- DNS
- MagicDNS
- Global and restricted nameservers (split DNS)
- search domains
- Extra DNS records (headscale only)
- Taildrop (File Sharing)
- Routing advertising (including exit nodes)
- Dual stack (IPv4 and IPv6)
- Ephemeral nodes
- Embedded DERP server
- Access control lists (GitHub label \"policy\")
- ACL management via API
-
autogroup:internet
-
autogroup:self
-
autogroup:member
- Node registration using Single-Sign-On (OpenID Connect) (GitHub label \"OIDC\")
- Basic registration
- Update user profile from identity provider
- Dynamic ACL support
- OIDC groups cannot be used in ACLs
- Funnel (#1040)
- Serve (#1234)
Join our Discord server for announcements and community support.
Please report bugs via GitHub issues
"},{"location":"about/releases/","title":"Releases","text":"All headscale releases are available on the GitHub release page. Those releases are available as binaries for various platforms and architectures, packages for Debian based systems and source code archives. Container images are available on Docker Hub.
An Atom/RSS feed of headscale releases is available here.
See the \"announcements\" channel on our Discord server for news about headscale.
"},{"location":"about/sponsor/","title":"Sponsor","text":"If you like to support the development of headscale, please consider a donation via ko-fi.com/headscale. Thank you!
"},{"location":"ref/acls/","title":"ACLs","text":"Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment.
For instance, instead of referring to users when defining groups you must use users (which are the equivalent to user/logins in Tailscale.com).
Please check https://tailscale.com/kb/1018/acls/ for further information.
When using ACL's the User borders are no longer applied. All machines whichever the User have the ability to communicate with other hosts as long as the ACL's permits this exchange.
"},{"location":"ref/acls/#acls-use-case-example","title":"ACLs use case example","text":"Let's build an example use case for a small business (It may be the place where ACL's are the most useful).
We have a small company with a boss, an admin, two developers and an intern.
The boss should have access to all servers but not to the user's hosts. Admin should also have access to all hosts except that their permissions should be limited to maintaining the hosts (for example purposes). The developers can do anything they want on dev hosts but only watch on productions hosts. Intern can only interact with the development servers.
There's an additional server that acts as a router, connecting the VPN users to an internal network
10.20.0.0/16
. Developers must have access to those internal resources.Each user have at least a device connected to the network and we have some servers.
- database.prod
- database.dev
- app-server1.prod
- app-server1.dev
- billing.internal
- router.internal
ACLs have to be written in huJSON.
When registering the servers we will need to add the flag
--advertise-tags=tag:<tag1>,tag:<tag2>
, and the user that is registering the server should be allowed to do it. Since anyone can add tags to a server they can register, the check of the tags is done on headscale server and only valid tags are applied. A tag is valid if the user that is registering it is allowed to do it.To use ACLs in headscale, you must edit your
config.yaml
file. In there you will find apolicy.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
"},{"location":"ref/configuration/","title":"Configuration","text":"{\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
View on GitHubDownload withmain
branch might contain unreleased changes.wget
Download withcurl
- 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
"},{"location":"ref/dns/","title":"DNS","text":"# 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
Headscale supports most DNS features from Tailscale. DNS releated settings can be configured within
"},{"location":"ref/dns/#setting-extra-dns-records","title":"Setting extra DNS records","text":"dns
section of the configuration file.Headscale allows to set extra DNS records which are made available via MagicDNS. Extra DNS records can be configured either via static entries in the configuration file or from a JSON file that Headscale continously watches for changes:
- Use the
dns.extra_records
option in the configuration file for entries that are static and don't change while Headscale is running. Those entries are processed when Headscale is starting up and changes to the configuration require a restart of Headscale. - For dynamic DNS records that may be added, updated or removed while Headscale is running or DNS records that are generated by scripts the option
dns.extra_records_path
in the configuration file is useful. Set it to the absolute path of the JSON file containing DNS records and Headscale processes this file as it detects changes.
An example use case is to serve multiple apps on the same host via a reverse proxy like NGINX, in this case a Prometheus monitoring stack. This allows to nicely access the service with \"http://grafana.myvpn.example.com\" instead of the hostname and port combination \"http://hostname-in-magic-dns.myvpn.example.com:3000\".
Limitations
Currently, only A and AAAA records are processed by Tailscale.
-
Configure extra DNS records using one of the available configuration options:
Static entries, viadns.extra_records
Dynamic entries, viadns.extra_records_path
config.yamldns:\n ...\n extra_records:\n - name: \"grafana.myvpn.example.com\"\n type: \"A\"\n value: \"100.64.0.3\"\n\n - name: \"prometheus.myvpn.example.com\"\n type: \"A\"\n value: \"100.64.0.3\"\n ...\n
Restart your headscale instance.
extra-records.json[\n {\n \"name\": \"grafana.myvpn.example.com\",\n \"type\": \"A\",\n \"value\": \"100.64.0.3\"\n },\n {\n \"name\": \"prometheus.myvpn.example.com\",\n \"type\": \"A\",\n \"value\": \"100.64.0.3\"\n }\n]\n
Headscale picks up changes to the above JSON file automatically.
Good to know
- The
dns.extra_records_path
option in the configuration file needs to reference the JSON file containing extra DNS records. - Be sure to \"sort keys\" and produce a stable output in case you generate the JSON file with a script. Headscale uses a checksum to detect changes to the file and a stable output avoids unnecessary processing.
- The
-
Verify that DNS records are properly set using the DNS querying tool of your choice:
Query with digQuery with drilldig +short grafana.myvpn.example.com\n100.64.0.3\n
drill -Q grafana.myvpn.example.com\n100.64.0.3\n
-
Optional: Setup the reverse proxy
The motivating example here was to be able to access internal monitoring services on the same host without specifying a port, depicted as NGINX configuration snippet:
nginx.confserver {\n listen 80;\n listen [::]:80;\n\n server_name grafana.myvpn.example.com;\n\n location / {\n proxy_pass http://localhost:3000;\n proxy_set_header Host $http_host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n }\n\n}\n
Register the node and make it advertise itself as an exit node:
$ sudo tailscale up --login-server https://headscale.example.com --advertise-exit-node\n
If the node is already registered, it can advertise exit capabilities like this:
$ sudo tailscale set --advertise-exit-node\n
To use a node as an exit node, IP forwarding must be enabled on the node. Check the official Tailscale documentation for how to enable IP forwarding.
"},{"location":"ref/exit-node/#on-the-control-server","title":"On the control server","text":"
"},{"location":"ref/exit-node/#on-the-client","title":"On the client","text":"$ # list nodes\n$ headscale routes list\nID | Node | Prefix | Advertised | Enabled | Primary\n1 | | 0.0.0.0/0 | false | false | -\n2 | | ::/0 | false | false | -\n3 | phobos | 0.0.0.0/0 | true | false | -\n4 | phobos | ::/0 | true | false | -\n\n$ # enable routes for phobos\n$ headscale routes enable -r 3\n$ headscale routes enable -r 4\n\n$ # Check node list again. The routes are now enabled.\n$ headscale routes list\nID | Node | Prefix | Advertised | Enabled | Primary\n1 | | 0.0.0.0/0 | false | false | -\n2 | | ::/0 | false | false | -\n3 | phobos | 0.0.0.0/0 | true | true | -\n4 | phobos | ::/0 | true | true | -\n
The exit node can now be used with:
$ sudo tailscale set --exit-node phobos\n
Check the official Tailscale documentation for how to do it on your device.
"},{"location":"ref/oidc/","title":"Configuring headscale to use OIDC authentication","text":"In order to authenticate users through a centralized solution one must enable the OIDC integration.
Known limitations:
- No dynamic ACL support
- OIDC groups cannot be used in ACLs
In your
config.yamlconfig.yaml
, customize this to your liking:
"},{"location":"ref/oidc/#azure-ad-example","title":"Azure AD example","text":"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
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.hclresource \"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.yamlconfig.yaml
:
"},{"location":"ref/oidc/#google-oauth-example","title":"Google OAuth Example","text":"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.
"},{"location":"ref/oidc/#steps","title":"Steps","text":"- Go to Google Console and login or create an account if you don't have one.
- Create a project (if you don't already have one).
- On the left hand menu, go to
APIs and services
->Credentials
- Click
Create Credentials
->OAuth client ID
- Under
Application Type
, chooseWeb Application
- For
Name
, enter whatever you like - Under
Authorised redirect URIs
, usehttps://example.com/oidc/callback
, replacing example.com with your headscale URL. - Click
Save
at the bottom of the form - Take note of the
Client ID
andClient secret
, you can also download it for reference if you need it. - Edit your headscale config, under
oidc
, filling in yourclient_id
andclient_secret
: config.yamloidc:\n issuer: \"https://accounts.google.com\"\n client_id: \"\"\n client_secret: \"\"\n scope: [\"openid\", \"profile\", \"email\"]\n
You can also use
"},{"location":"ref/remote-cli/","title":"Controlling headscale with remote CLI","text":"allowed_domains
andallowed_users
to restrict the users who can authenticate.This documentation has the goal of showing a user how-to control a headscale instance from a remote machine with the
"},{"location":"ref/remote-cli/#prerequisite","title":"Prerequisite","text":"headscale
command line binary.- 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.
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:
"},{"location":"ref/remote-cli/#download-and-configure-headscale","title":"Download and configure headscale","text":"headscale apikeys expire --prefix \"<PREFIX>\"\n
-
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:
Minimal YAML configuration fileEnvironment variables config.yamlcli:\n address: <HEADSCALE_ADDRESS>:<PORT>\n api_key: <API_KEY_FROM_PREVIOUS_STEP>\n
export HEADSCALE_CLI_ADDRESS=\"<HEADSCALE_ADDRESS>:<PORT>\"\nexport HEADSCALE_CLI_API_KEY=\"<API_KEY_FROM_PREVIOUS_STEP>\"\n
Bug
Headscale currently requires at least an empty configuration file when environment variables are used to specify connection details. See issue 2193 for more information.
This instructs the
headscale
binary to connect to a remote instance at<HEADSCALE_ADDRESS>:<PORT>
, instead of connecting to the local instance. -
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.
It is possible to run the gRPC remote endpoint behind a reverse proxy, like Nginx, and have it run on the same port as headscale.
While this is not a supported feature, an example on how this can be set up on NixOS is shown here.
"},{"location":"ref/remote-cli/#troubleshooting","title":"Troubleshooting","text":"- Make sure you have the same headscale version on your server and workstation.
- Ensure that connections to the gRPC port are allowed.
- Verify that your TLS certificate is valid and trusted.
- If you don't have access to a trusted certificate (e.g. from Let's Encrypt), either:
- Add your self-signed certificate to the trust store of your OS or
- Disable certificate verification by either setting
cli.insecure: true
in the configuration file or by settingHEADSCALE_CLI_INSECURE=1
via an environment variable. We do not recommend to disable certificate validation.
Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the
config.yamltls_cert_path
andtls_cert_path
configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.tls_cert_path: \"\"\ntls_key_path: \"\"\n
The certificate should contain the full chain, else some clients, like the Tailscale Android client, will reject it.
"},{"location":"ref/tls/#lets-encrypt-acme","title":"Let's Encrypt / ACME","text":"To get a certificate automatically via Let's Encrypt, set
config.yamltls_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 theserver_url
configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured intls_letsencrypt_cache_dir
. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.
"},{"location":"ref/tls/#challenge-types","title":"Challenge types","text":"tls_letsencrypt_hostname: \"\"\ntls_letsencrypt_listen: \":http\"\ntls_letsencrypt_cache_dir: \".cache\"\ntls_letsencrypt_challenge_type: HTTP-01\n
Headscale only supports two values for
"},{"location":"ref/tls/#http-01","title":"HTTP-01","text":"tls_letsencrypt_challenge_type
:HTTP-01
(default) andTLS-ALPN-01
.For
HTTP-01
, headscale must be reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured inlisten_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
"},{"location":"ref/tls/#tls-alpn-01","title":"TLS-ALPN-01","text":"tls_letsencrypt_listen
to the appropriate value. This can be handy if you are running headscale as a non-root user (or can't runsetcap
). Keep in mind, however, that Let's Encrypt will only connect to port 80 for the validation callback, so if you changetls_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 intls_letsencrypt_listen
.For
"},{"location":"ref/tls/#technical-description","title":"Technical description","text":"TLS-ALPN-01
, headscale listens on the ip:port combination defined inlisten_addr
. Let's Encrypt will only connect to port 443 for the validation callback, so iflisten_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 inlisten_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.
- Or, check remotely from CLI using
openssl
:
"},{"location":"ref/tls/#log-output-from-the-autocert-library","title":"Log output from the autocert library","text":"$ openssl s_client -servername [hostname] -connect [hostname]:443 | openssl x509 -noout -dates\n(...)\nnotBefore=Feb 8 09:48:26 2024 GMT\nnotAfter=May 8 09:48:25 2024 GMT\n
As these log lines are from the autocert library, they are not strictly generated by headscale itself.
acme/autocert: missing server name\n
Likely caused by an incoming connection that does not specify a hostname, for example a
curl
request directly against the IP of the server, or an unexpected hostname.acme/autocert: host \"[foo]\" not configured in HostWhitelist\n
Similarly to the above, this likely indicates an invalid incoming request for an incorrect hostname, commonly just the IP itself.
The source code for autocert can be found here
"},{"location":"ref/integration/reverse-proxy/","title":"Running headscale behind a reverse proxy","text":"Community documentation
This page is not actively maintained by the headscale authors and is written by community members. It is not verified by headscale developers.
It might be outdated and it might miss necessary steps.
Running headscale behind a reverse proxy is useful when running multiple applications on the same server, and you want to reuse the same external IP and port - usually tcp/443 for HTTPS.
"},{"location":"ref/integration/reverse-proxy/#websockets","title":"WebSockets","text":"The reverse proxy MUST be configured to support WebSockets to communicate with Tailscale clients.
WebSockets support is also required when using the headscale embedded DERP server. In this case, you will also need to expose the UDP port used for STUN (by default, udp/3478). Please check our config-example.yaml.
"},{"location":"ref/integration/reverse-proxy/#cloudflare","title":"Cloudflare","text":"Running headscale behind a cloudflare proxy or cloudflare tunnel is not supported and will not work as Cloudflare does not support WebSocket POSTs as required by the Tailscale protocol. See this issue
"},{"location":"ref/integration/reverse-proxy/#tls","title":"TLS","text":"Headscale can be configured not to use TLS, leaving it to the reverse proxy to handle. Add the following configuration values to your headscale config file.
config.yaml
"},{"location":"ref/integration/reverse-proxy/#nginx","title":"nginx","text":"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.
nginx.conf<IP:PORT>
should be the IP address and port where headscale is running. In most cases, this will behttp://localhost:8080
.
"},{"location":"ref/integration/reverse-proxy/#istioenvoy","title":"istio/envoy","text":"map $http_upgrade $connection_upgrade {\n default upgrade;\n '' close;\n}\n\nserver {\n listen 80;\n listen [::]:80;\n\n listen 443 ssl http2;\n listen [::]:443 ssl http2;\n\n server_name <YOUR_SERVER_NAME>;\n\n ssl_certificate <PATH_TO_CERT>;\n ssl_certificate_key <PATH_CERT_KEY>;\n ssl_protocols TLSv1.2 TLSv1.3;\n\n location / {\n proxy_pass http://<IP:PORT>;\n proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection $connection_upgrade;\n proxy_set_header Host $server_name;\n proxy_redirect http:// https://;\n proxy_buffering off;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n add_header Strict-Transport-Security \"max-age=15552000; includeSubDomains\" always;\n }\n}\n
If you using Istio ingressgateway or Envoy as reverse proxy, there are some tips for you. If not set, you may see some debug log in proxy as below:
"},{"location":"ref/integration/reverse-proxy/#envoy","title":"Envoy","text":"Sending local reply with details upgrade_failed\n
You need to add a new upgrade_type named
"},{"location":"ref/integration/reverse-proxy/#istio","title":"Istio","text":"tailscale-control-protocol
. see detailsSame as envoy, we can use
EnvoyFilter
to add upgrade_type.
"},{"location":"ref/integration/reverse-proxy/#caddy","title":"Caddy","text":"apiVersion: networking.istio.io/v1alpha3\nkind: EnvoyFilter\nmetadata:\n name: headscale-behind-istio-ingress\n namespace: istio-system\nspec:\n configPatches:\n - applyTo: NETWORK_FILTER\n match:\n listener:\n filterChain:\n filter:\n name: envoy.filters.network.http_connection_manager\n patch:\n operation: MERGE\n value:\n typed_config:\n \"@type\": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager\n upgrade_configs:\n - upgrade_type: tailscale-control-protocol\n
The following Caddyfile is all that is necessary to use Caddy as a reverse proxy for headscale, in combination with the
Caddyfileconfig.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 belocalhost:8080
.<YOUR_SERVER_NAME> {\n reverse_proxy <IP:PORT>\n}\n
Caddy v2 will automatically provision a certificate for your domain/subdomain, force HTTPS, and proxy websockets - no further configuration is necessary.
For a slightly more complex configuration which utilizes Docker containers to manage Caddy, headscale, and Headscale-UI, Guru Computing's guide is an excellent reference.
"},{"location":"ref/integration/reverse-proxy/#apache","title":"Apache","text":"The following minimal Apache config will proxy traffic to the headscale instance on
apache.conf<IP:PORT>
. Note thatupgrade=any
is required as a parameter forProxyPass
so that WebSockets traffic whoseUpgrade
header value is not equal toWebSocket
(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":"<VirtualHost *:443>\n ServerName <YOUR_SERVER_NAME>\n\n ProxyPreserveHost On\n ProxyPass / http://<IP:PORT>/ upgrade=any\n\n SSLEngine On\n SSLCertificateFile <PATH_TO_CERT>\n SSLCertificateKeyFile <PATH_CERT_KEY>\n</VirtualHost>\n
Community contributions
This page contains community contributions. The projects listed here are not maintained by the headscale authors and are written by community members.
This page collects third-party tools and scripts related to headscale.
Name Repository Link Description tailscale-manager Github Dynamically manage Tailscale route advertisements headscalebacktosqlite Github Migrate headscale from PostgreSQL back to SQLite"},{"location":"ref/integration/web-ui/","title":"Web interfaces for headscale","text":"Community contributions
This page contains community contributions. The projects listed here are not maintained by the headscale authors and are written by community members.
Headscale doesn't provide a built-in web interface but users may pick one from the available options.
Name Repository Link Description headscale-webui Github A simple headscale web UI for small-scale deployments. headscale-ui Github A web frontend for the headscale Tailscale-compatible coordination server HeadscaleUi GitHub A static headscale admin ui, no backend enviroment required Headplane GitHub An advanced Tailscale inspired frontend for headscale headscale-admin Github Headscale-Admin is meant to be a simple, modern web interface for headscale ouroboros Github Ouroboros is designed for users to manage their own devices, rather than for adminsYou can ask for support on our Discord server in the \"web-interfaces\" channel.
"},{"location":"setup/requirements/","title":"Requirements","text":"Headscale should just work as long as the following requirements are met:
- A server with a public IP address for headscale. A dual-stack setup with a public IPv4 and a public IPv6 address is recommended.
- Headscale is served via HTTPS on port 4431.
- A reasonably modern Linux or BSD based operating system.
- A dedicated user account to run headscale.
- A little bit of command line knowledge to configure and operate headscale.
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 asheadscale.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
Update an existing headscale installation to a new version:
- Read the announcement on the GitHub releases page for the new version. It lists the changes of the release along with possible breaking changes.
- Create a backup of your database.
- Update headscale to the new version, preferably by following the same installation method.
- Compare and update the configuration file.
- Restart headscale.
Several Linux distributions and community members provide packages for headscale. Those packages may be used instead of the official releases provided by the headscale maintainers. Such packages offer improved integration for their targeted operating system and usually:
- setup a dedicated user account to run headscale
- provide a default configuration
- install headscale as system service
Community packages might be outdated
The packages mentioned on this page might be outdated or unmaintained. Use the official releases to get the current stable version or to test pre-releases.
"},{"location":"setup/install/community/#arch-linux","title":"Arch Linux","text":"Arch Linux offers a package for headscale, install via:
pacman -S headscale\n
The AUR package
"},{"location":"setup/install/community/#fedora-rhel-centos","title":"Fedora, RHEL, CentOS","text":"headscale-git
can be used to build the current development version.A third-party repository for various RPM based distributions is available at: https://copr.fedorainfracloud.org/coprs/jonathanspw/headscale/. The site provides detailed setup and installation instructions.
"},{"location":"setup/install/community/#nix-nixos","title":"Nix, NixOS","text":"A Nix package is available as:
"},{"location":"setup/install/community/#gentoo","title":"Gentoo","text":"headscale
. See the NixOS package site for installation details.emerge --ask net-vpn/headscale\n
Gentoo specific documentation is available here.
"},{"location":"setup/install/community/#openbsd","title":"OpenBSD","text":"Headscale is available in ports. The port installs headscale as system service with
rc.d
and provides usage instructions upon installation.
"},{"location":"setup/install/container/","title":"Running headscale in a container","text":"pkg_add headscale\n
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.sudo mkdir -p /etc/headscale\nsudo nano /etc/headscale/config.yaml\n
Alternatively, you can mount
/var/lib
and/var/run
from your host system by adding--volume $(pwd)/lib:/var/lib/headscale
and--volume $(pwd)/run:/var/run/headscale
in the next step. -
Start the headscale server while working in the host headscale directory:
docker run \\\n --name headscale \\\n --detach \\\n --volume $(pwd)/config:/etc/headscale/ \\\n --publish 127.0.0.1:8080:8080 \\\n --publish 127.0.0.1:9090:9090 \\\n headscale/headscale:<VERSION> \\\n serve\n
Note: use
0.0.0.0:8080:8080
instead of127.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
-
Verify headscale is running:
Follow the container logs:
docker logs --follow headscale\n
Verify running containers:
docker ps\n
Verify headscale is available:
curl http://127.0.0.1:9090/metrics\n
-
Create a user (tailnet):
docker exec -it headscale \\\n headscale users create myfirstuser\n
On a client machine, execute the
tailscale
login command:tailscale up --login-server YOUR_HEADSCALE_URL\n
To register a machine when running headscale in a container, take the headscale command and pass it to the container:
"},{"location":"setup/install/container/#register-machine-using-a-pre-authenticated-key","title":"Register machine using a pre authenticated key","text":"docker exec -it headscale \\\n headscale nodes register --user myfirstuser --key <YOUR_MACHINE_KEY>\n
Generate a key using the command line:
docker exec -it headscale \\\n headscale preauthkeys create --user myfirstuser --reusable --expiration 24h\n
This will return a pre-authenticated key that can be used to connect a node to headscale during the
tailscale
command:
"},{"location":"setup/install/container/#debugging-headscale-running-in-docker","title":"Debugging headscale running in Docker","text":"tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\n
The
"},{"location":"setup/install/container/#running-the-debug-docker-container","title":"Running the debug Docker container","text":"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 exampleheadscale/headscale:x.x.x-debug
.To run the debug Docker container, use the exact same commands as above, but replace
"},{"location":"setup/install/container/#executing-commands-in-the-debug-container","title":"Executing commands in the debug container","text":"headscale/headscale:x.x.x
withheadscale/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.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
"},{"location":"setup/install/official/","title":"Official releases","text":"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:
sudo nano /etc/headscale/config.yaml\n
-
Enable and start the headscale service:
sudo systemctl enable --now headscale\n
-
Verify that headscale is running as intended:
sudo systemctl status headscale\n
Advanced
This installation method is considered advanced as one needs to take care of the headscale user and the systemd service themselves. If possible, use the DEB packages or a community package instead.
This section describes the installation of headscale according to the Requirements and assumptions. Headscale is run by a dedicated user and the service itself is managed by systemd.
-
Download the latest
headscale
binary from GitHub's release page:sudo wget --output-document=/usr/local/bin/headscale \\\nhttps://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>\n
-
Make
headscale
executable:sudo chmod +x /usr/local/bin/headscale\n
-
Add a dedicated user to run headscale:
sudo useradd \\\n --create-home \\\n --home-dir /var/lib/headscale/ \\\n --system \\\n --user-group \\\n --shell /usr/sbin/nologin \\\n headscale\n
-
Download the example configuration for your chosen version and save it as:
/etc/headscale/config.yaml
. Adjust the configuration to suit your local environment. See Configuration for details.sudo mkdir -p /etc/headscale\nsudo nano /etc/headscale/config.yaml\n
-
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
config.yaml/etc/headscale/config.yaml
, override the defaultheadscale
unix socket with a path that is writable by theheadscale
user or group: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
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":"
"},{"location":"setup/install/source/#install-from-source-via-cross-compile","title":"Install from source via cross compile","text":"# Install prerequistes\npkg_add go\n\ngit clone https://github.com/juanfont/headscale.git\n\ncd headscale\n\n# optionally checkout a release\n# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest\n# option b. get latest tag, this may be a beta release\nlatestTag=$(git describe --tags `git rev-list --tags --max-count=1`)\n\ngit checkout $latestTag\n\ngo build -ldflags=\"-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$latestTag\" github.com/juanfont/headscale\n\n# make it executable\nchmod a+x headscale\n\n# copy it to /usr/local/sbin\ncp headscale /usr/local/sbin\n
"},{"location":"usage/getting-started/","title":"Getting started","text":"# Install prerequistes\n# 1. go v1.20+: headscale newer than 0.21 needs go 1.20+ to compile\n# 2. gmake: Makefile in the headscale repo is written in GNU make syntax\n\ngit clone https://github.com/juanfont/headscale.git\n\ncd headscale\n\n# optionally checkout a release\n# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest\n# option b. get latest tag, this may be a beta release\nlatestTag=$(git describe --tags `git rev-list --tags --max-count=1`)\n\ngit checkout $latestTag\n\nmake build GOOS=openbsd\n\n# copy headscale to openbsd machine and put it in /usr/local/sbin\n
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
NativeContainerheadscale
command line tool provides built-in help. To show available commands along with their arguments and options, run:# Show help\nheadscale help\n\n# Show help for a specific command\nheadscale <COMMAND> --help\n
"},{"location":"usage/getting-started/#manage-users","title":"Manage users","text":"# 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
"},{"location":"usage/getting-started/#create-a-user","title":"Create a user","text":"NativeContainerheadscale users
command. Invoke the built-in help for more information:headscale users --help
.headscale users create <USER>\n
"},{"location":"usage/getting-started/#list-existing-users","title":"List existing users","text":"NativeContainerdocker exec -it headscale \\\n headscale users create <USER>\n
headscale users list\n
"},{"location":"usage/getting-started/#register-a-node","title":"Register a node","text":"docker exec -it headscale \\\n headscale users list\n
One has to register a node first to use headscale as coordination with Tailscale. The following examples work for the Tailscale client on Linux/BSD operating systems. Alternatively, follow the instructions to connect Android, Apple or Windows devices.
"},{"location":"usage/getting-started/#normal-interactive-login","title":"Normal, interactive login","text":"On a client machine, run the
tailscale up
command and provide the FQDN of your headscale instance as argument:tailscale up --login-server <YOUR_HEADSCALE_URL>\n
Usually, a browser window with further instructions is opened and contains the value for
NativeContainer<YOUR_MACHINE_KEY>
. Approve and register the node on your headscale server:headscale nodes register --user <USER> --key <YOUR_MACHINE_KEY>\n
"},{"location":"usage/getting-started/#using-a-preauthkey","title":"Using a preauthkey","text":"docker exec -it headscale \\\n headscale nodes register --user <USER> --key <YOUR_MACHINE_KEY>\n
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
NativeContainerheadscale preauthkeys --help
for other options):headscale preauthkeys create --user <USER>\n
docker exec -it headscale \\\n headscale preauthkeys create --user <USER>\n
The command returns the preauthkey on success which is used to connect a node to the headscale instance via the
tailscale up
command:
"},{"location":"usage/connect/android/","title":"Connecting an Android client","text":"tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\n
This documentation has the goal of showing how a user can use the official Android Tailscale client with headscale.
"},{"location":"usage/connect/android/#installation","title":"Installation","text":"Install the official Tailscale Android client from the Google Play Store or F-Droid.
"},{"location":"usage/connect/android/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"- Open the app and select the settings menu in the upper-right corner
- Tap on
Accounts
- In the kebab menu icon (three dots) in the upper-right corner select
Use an alternate server
- Enter your server URL (e.g
https://headscale.example.com
) and follow the instructions
This documentation has the goal of showing how a user can use the official iOS and macOS Tailscale clients with headscale.
Instructions on your headscale instance
An endpoint with information on how to connect your Apple device is also available at
"},{"location":"usage/connect/apple/#ios","title":"iOS","text":""},{"location":"usage/connect/apple/#installation","title":"Installation","text":"/apple
on your running instance.Install the official Tailscale iOS client from the App Store.
"},{"location":"usage/connect/apple/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"- Open Tailscale and make sure you are not logged in to any account
- Open Settings on the iOS device
- Scroll down to the
third party apps
section, underGame Center
orTV Provider
- Find Tailscale and select it
- If the iOS device was previously logged into Tailscale, switch the
Reset Keychain
toggle toon
- Enter the URL of your headscale instance (e.g
https://headscale.example.com
) underAlternate Coordination Server URL
- Restart the app by closing it from the iOS app switcher, open the app and select the regular sign in option (non-SSO). It should open up to the headscale authentication page.
- Enter your credentials and log in. Headscale should now be working on your iOS device.
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
):
"},{"location":"usage/connect/apple/#gui","title":"GUI","text":"tailscale login --login-server <YOUR_HEADSCALE_URL>\n
- Option + Click the Tailscale icon in the menu and hover over the Debug menu
- Under
Custom Login Server
, selectAdd Account...
- Enter the URL of your headscale instance (e.g
https://headscale.example.com
) and pressAdd Account
- Follow the login procedure in the browser
Install the official Tailscale tvOS client from the App Store.
Danger
Don't open the Tailscale App after installation!
"},{"location":"usage/connect/apple/#configuring-the-headscale-url_2","title":"Configuring the headscale URL","text":"- Open Settings (the Apple tvOS settings) > Apps > Tailscale
- Under
ALTERNATE COORDINATION SERVER URL
, selectURL
- Enter the URL of your headscale instance (e.g
https://headscale.example.com
) and pressOK
- Return to the tvOS Home screen
- Open Tailscale
- Click the button
Install VPN configuration
and confirm the appearing popup by clicking theAllow
button - Scan the QR code and follow the login procedure
This documentation has the goal of showing how a user can use the official Windows Tailscale client with headscale.
Instructions on your headscale instance
An endpoint with information on how to connect your Windows device is also available at
"},{"location":"usage/connect/windows/#installation","title":"Installation","text":"/windows
on your running instance.Download the Official Windows Client and install it.
"},{"location":"usage/connect/windows/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"Open a Command Prompt or Powershell and use Tailscale's login command to connect with your headscale instance (e.g
https://headscale.example.com
):tailscale login --login-server <YOUR_HEADSCALE_URL>\n
Follow the instructions in the opened browser window to finish the configuration.
"},{"location":"usage/connect/windows/#troubleshooting","title":"Troubleshooting","text":""},{"location":"usage/connect/windows/#unattended-mode","title":"Unattended mode","text":"By default, Tailscale's Windows client is only running when the user is logged in. If you want to keep Tailscale running all the time, please enable \"Unattended mode\":
- Click on the Tailscale tray icon and select
Preferences
- Enable
Run unattended
- Confirm the \"Unattended mode\" message
See also Keep Tailscale running when I'm not logged in to my computer
"},{"location":"usage/connect/windows/#failing-node-registration","title":"Failing node registration","text":"If you are seeing repeated messages like:
[GIN] 2022/02/10 - 16:39:34 | 200 | 1.105306ms | 127.0.0.1 | POST \"/machine/redacted\"\n
in your headscale output, turn on
DEBUG
logging and look for:2022-02-11T00:59:29Z DBG Machine registration has expired. Sending a authurl to register machine=redacted\n
This typically means that the registry keys above was not set appropriately.
To reset and try again, it is important to do the following:
- 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.
Headscale is an open source, self-hosted implementation of the Tailscale control server.
This page contains the documentation for the latest version of headscale. Please also check our FAQ.
Join our Discord server for a chat and community support.
"},{"location":"#design-goal","title":"Design goal","text":"Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. It implements a narrower scope, a single Tailnet, suitable for a personal use, or a small open-source organisation.
"},{"location":"#supporting-headscale","title":"Supporting headscale","text":"Please see Sponsor for more information.
"},{"location":"#contributing","title":"Contributing","text":"Headscale is \"Open Source, acknowledged contribution\", this means that any contribution will have to be discussed with the Maintainers before being submitted.
Please see Contributing for more information.
"},{"location":"#about","title":"About","text":"Headscale is maintained by Kristoffer Dalby and Juan Font.
"},{"location":"about/clients/","title":"Client and operating system support","text":"We aim to support the last 10 releases of the Tailscale client on all provided operating systems and platforms. Some platforms might require additional configuration to connect with headscale.
OS Supports headscale Linux Yes OpenBSD Yes FreeBSD Yes Windows Yes (see docs and/windows
on your headscale for more information) Android Yes (see docs) macOS Yes (see docs and/apple
on your headscale for more information) iOS Yes (see docs and/apple
on your headscale for more information) tvOS Yes (see docs and/apple
on your headscale for more information)"},{"location":"about/contributing/","title":"Contributing","text":"Headscale is \"Open Source, acknowledged contribution\", this means that any contribution will have to be discussed with the maintainers before being added to the project. This model has been chosen to reduce the risk of burnout by limiting the maintenance overhead of reviewing and validating third-party code.
"},{"location":"about/contributing/#why-do-we-have-this-model","title":"Why do we have this model?","text":"Headscale has a small maintainer team that tries to balance working on the project, fixing bugs and reviewing contributions.
When we work on issues ourselves, we develop first hand knowledge of the code and it makes it possible for us to maintain and own the code as the project develops.
Code contributions are seen as a positive thing. People enjoy and engage with our project, but it also comes with some challenges; we have to understand the code, we have to understand the feature, we might have to become familiar with external libraries or services and we think about security implications. All those steps are required during the reviewing process. After the code has been merged, the feature has to be maintained. Any changes reliant on external services must be updated and expanded accordingly.
The review and day-1 maintenance adds a significant burden on the maintainers. Often we hope that the contributor will help out, but we found that most of the time, they disappear after their new feature was added.
This means that when someone contributes, we are mostly happy about it, but we do have to run it through a series of checks to establish if we actually can maintain this feature.
"},{"location":"about/contributing/#what-do-we-require","title":"What do we require?","text":"A general description is provided here and an explicit list is provided in our pull request template.
All new features have to start out with a design document, which should be discussed on the issue tracker (not discord). It should include a use case for the feature, how it can be implemented, who will implement it and a plan for maintaining it.
All features have to be end-to-end tested (integration tests) and have good unit test coverage to ensure that they work as expected. This will also ensure that the feature continues to work as expected over time. If a change cannot be tested, a strong case for why this is not possible needs to be presented.
The contributor should help to maintain the feature over time. In case the feature is not maintained probably, the maintainers reserve themselves the right to remove features they redeem as unmaintainable. This should help to improve the quality of the software and keep it in a maintainable state.
"},{"location":"about/contributing/#bug-fixes","title":"Bug fixes","text":"Headscale is open to code contributions for bug fixes without discussion.
"},{"location":"about/contributing/#documentation","title":"Documentation","text":"If you find mistakes in the documentation, please submit a fix to the documentation.
"},{"location":"about/faq/","title":"Frequently Asked Questions","text":""},{"location":"about/faq/#what-is-the-design-goal-of-headscale","title":"What is the design goal of headscale?","text":"Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. It implements a narrow scope, a single Tailnet, suitable for a personal use, or a small open-source organisation.
"},{"location":"about/faq/#how-can-i-contribute","title":"How can I contribute?","text":"Headscale is \"Open Source, acknowledged contribution\", this means that any contribution will have to be discussed with the Maintainers before being submitted.
Please see Contributing for more information.
"},{"location":"about/faq/#why-is-acknowledged-contribution-the-chosen-model","title":"Why is 'acknowledged contribution' the chosen model?","text":"Both maintainers have full-time jobs and families, and we want to avoid burnout. We also want to avoid frustration from contributors when their PRs are not accepted.
We are more than happy to exchange emails, or to have dedicated calls before a PR is submitted.
"},{"location":"about/faq/#whenwhy-is-feature-x-going-to-be-implemented","title":"When/Why is Feature X going to be implemented?","text":"We don't know. We might be working on it. If you're interested in contributing, please post a feature request about it.
Please be aware that there are a number of reasons why we might not accept specific contributions:
- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
- Given that we are reverse-engineering Tailscale to satisfy our own curiosity, we might be interested in implementing the feature ourselves.
- You are not sending unit and integration tests with it.
We currently support deploying headscale using our binaries and the DEB packages. Visit our installation guide using official releases for more information.
In addition to that, you may use packages provided by the community or from distributions. Learn more in the installation guide using community packages.
For convenience, we also build Docker images with headscale. But please be aware that we don't officially support deploying headscale using Docker. On our Discord server we have a \"docker-issues\" channel where you can ask for Docker-specific help to the community.
"},{"location":"about/faq/#which-database-should-i-use","title":"Which database should I use?","text":"We recommend the use of SQLite as database for headscale:
- SQLite is simple to setup and easy to use
- It scales well for all of headscale's usecases
- Development and testing happens primarily on SQLite
- PostgreSQL is still supported, but is considered to be in \"maintenance mode\"
The headscale project itself does not provide a tool to migrate from PostgreSQL to SQLite. Please have a look at the related tools documentation for migration tooling provided by the community.
"},{"location":"about/faq/#why-is-my-reverse-proxy-not-working-with-headscale","title":"Why is my reverse proxy not working with headscale?","text":"We don't know. We don't use reverse proxies with headscale ourselves, so we don't have any experience with them. We have community documentation on how to configure various reverse proxies, and a dedicated \"reverse-proxy-issues\" channel on our Discord server where you can ask for help to the community.
"},{"location":"about/faq/#can-i-use-headscale-and-tailscale-on-the-same-machine","title":"Can I use headscale and tailscale on the same machine?","text":"Running headscale on a machine that is also in the tailnet can cause problems with subnet routers, traffic relay nodes, and MagicDNS. It might work, but it is not supported.
"},{"location":"about/features/","title":"Features","text":"Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. This page provides on overview of headscale's feature and compatibility with the Tailscale control server:
- Full \"base\" support of Tailscale's features
- Node registration
- Interactive
- Pre authenticated key
- DNS
- MagicDNS
- Global and restricted nameservers (split DNS)
- search domains
- Extra DNS records (headscale only)
- Taildrop (File Sharing)
- Routing advertising (including exit nodes)
- Dual stack (IPv4 and IPv6)
- Ephemeral nodes
- Embedded DERP server
- Access control lists (GitHub label \"policy\")
- ACL management via API
-
autogroup:internet
-
autogroup:self
-
autogroup:member
- Node registration using Single-Sign-On (OpenID Connect) (GitHub label \"OIDC\")
- Basic registration
- Update user profile from identity provider
- Dynamic ACL support
- OIDC groups cannot be used in ACLs
- Funnel (#1040)
- Serve (#1234)
Join our Discord server for announcements and community support.
Please report bugs via GitHub issues
"},{"location":"about/releases/","title":"Releases","text":"All headscale releases are available on the GitHub release page. Those releases are available as binaries for various platforms and architectures, packages for Debian based systems and source code archives. Container images are available on Docker Hub.
An Atom/RSS feed of headscale releases is available here.
See the \"announcements\" channel on our Discord server for news about headscale.
"},{"location":"about/sponsor/","title":"Sponsor","text":"If you like to support the development of headscale, please consider a donation via ko-fi.com/headscale. Thank you!
"},{"location":"ref/acls/","title":"ACLs","text":"Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment.
For instance, instead of referring to users when defining groups you must use users (which are the equivalent to user/logins in Tailscale.com).
Please check https://tailscale.com/kb/1018/acls/ for further information.
When using ACL's the User borders are no longer applied. All machines whichever the User have the ability to communicate with other hosts as long as the ACL's permits this exchange.
"},{"location":"ref/acls/#acls-use-case-example","title":"ACLs use case example","text":"Let's build an example use case for a small business (It may be the place where ACL's are the most useful).
We have a small company with a boss, an admin, two developers and an intern.
The boss should have access to all servers but not to the user's hosts. Admin should also have access to all hosts except that their permissions should be limited to maintaining the hosts (for example purposes). The developers can do anything they want on dev hosts but only watch on productions hosts. Intern can only interact with the development servers.
There's an additional server that acts as a router, connecting the VPN users to an internal network
10.20.0.0/16
. Developers must have access to those internal resources.Each user have at least a device connected to the network and we have some servers.
- database.prod
- database.dev
- app-server1.prod
- app-server1.dev
- billing.internal
- router.internal
ACLs have to be written in huJSON.
When registering the servers we will need to add the flag
--advertise-tags=tag:<tag1>,tag:<tag2>
, and the user that is registering the server should be allowed to do it. Since anyone can add tags to a server they can register, the check of the tags is done on headscale server and only valid tags are applied. A tag is valid if the user that is registering it is allowed to do it.To use ACLs in headscale, you must edit your
config.yaml
file. In there you will find apolicy.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
"},{"location":"ref/configuration/","title":"Configuration","text":"{\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
View on GitHubDownload withmain
branch might contain unreleased changes.wget
Download withcurl
- 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
"},{"location":"ref/dns/","title":"DNS","text":"# 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
Headscale supports most DNS features from Tailscale. DNS related settings can be configured within
"},{"location":"ref/dns/#setting-extra-dns-records","title":"Setting extra DNS records","text":"dns
section of the configuration file.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, viadns.extra_records
Dynamic entries, viadns.extra_records_path
config.yamldns:\n ...\n extra_records:\n - name: \"grafana.myvpn.example.com\"\n type: \"A\"\n value: \"100.64.0.3\"\n\n - name: \"prometheus.myvpn.example.com\"\n type: \"A\"\n value: \"100.64.0.3\"\n ...\n
Restart your headscale instance.
extra-records.json[\n {\n \"name\": \"grafana.myvpn.example.com\",\n \"type\": \"A\",\n \"value\": \"100.64.0.3\"\n },\n {\n \"name\": \"prometheus.myvpn.example.com\",\n \"type\": \"A\",\n \"value\": \"100.64.0.3\"\n }\n]\n
Headscale picks up changes to the above JSON file automatically.
Good to know
- The
dns.extra_records_path
option in the configuration file needs to reference the JSON file containing extra DNS records. - Be sure to \"sort keys\" and produce a stable output in case you generate the JSON file with a script. Headscale uses a checksum to detect changes to the file and a stable output avoids unnecessary processing.
- The
-
Verify that DNS records are properly set using the DNS querying tool of your choice:
Query with digQuery with drilldig +short grafana.myvpn.example.com\n100.64.0.3\n
drill -Q grafana.myvpn.example.com\n100.64.0.3\n
-
Optional: Setup the reverse proxy
The motivating example here was to be able to access internal monitoring services on the same host without specifying a port, depicted as NGINX configuration snippet:
nginx.confserver {\n listen 80;\n listen [::]:80;\n\n server_name grafana.myvpn.example.com;\n\n location / {\n proxy_pass http://localhost:3000;\n proxy_set_header Host $http_host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n }\n\n}\n
Register the node and make it advertise itself as an exit node:
$ sudo tailscale up --login-server https://headscale.example.com --advertise-exit-node\n
If the node is already registered, it can advertise exit capabilities like this:
$ sudo tailscale set --advertise-exit-node\n
To use a node as an exit node, IP forwarding must be enabled on the node. Check the official Tailscale documentation for how to enable IP forwarding.
"},{"location":"ref/exit-node/#on-the-control-server","title":"On the control server","text":"
"},{"location":"ref/exit-node/#on-the-client","title":"On the client","text":"$ # list nodes\n$ headscale routes list\nID | Node | Prefix | Advertised | Enabled | Primary\n1 | | 0.0.0.0/0 | false | false | -\n2 | | ::/0 | false | false | -\n3 | phobos | 0.0.0.0/0 | true | false | -\n4 | phobos | ::/0 | true | false | -\n\n$ # enable routes for phobos\n$ headscale routes enable -r 3\n$ headscale routes enable -r 4\n\n$ # Check node list again. The routes are now enabled.\n$ headscale routes list\nID | Node | Prefix | Advertised | Enabled | Primary\n1 | | 0.0.0.0/0 | false | false | -\n2 | | ::/0 | false | false | -\n3 | phobos | 0.0.0.0/0 | true | true | -\n4 | phobos | ::/0 | true | true | -\n
The exit node can now be used with:
$ sudo tailscale set --exit-node phobos\n
Check the official Tailscale documentation for how to do it on your device.
"},{"location":"ref/oidc/","title":"Configuring headscale to use OIDC authentication","text":"In order to authenticate users through a centralized solution one must enable the OIDC integration.
Known limitations:
- No dynamic ACL support
- OIDC groups cannot be used in ACLs
In your
config.yamlconfig.yaml
, customize this to your liking:
"},{"location":"ref/oidc/#azure-ad-example","title":"Azure AD example","text":"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
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.hclresource \"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.yamlconfig.yaml
:
"},{"location":"ref/oidc/#google-oauth-example","title":"Google OAuth Example","text":"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.
"},{"location":"ref/oidc/#steps","title":"Steps","text":"- Go to Google Console and login or create an account if you don't have one.
- Create a project (if you don't already have one).
- On the left hand menu, go to
APIs and services
->Credentials
- Click
Create Credentials
->OAuth client ID
- Under
Application Type
, chooseWeb Application
- For
Name
, enter whatever you like - Under
Authorised redirect URIs
, usehttps://example.com/oidc/callback
, replacing example.com with your headscale URL. - Click
Save
at the bottom of the form - Take note of the
Client ID
andClient secret
, you can also download it for reference if you need it. - Edit your headscale config, under
oidc
, filling in yourclient_id
andclient_secret
: config.yamloidc:\n issuer: \"https://accounts.google.com\"\n client_id: \"\"\n client_secret: \"\"\n scope: [\"openid\", \"profile\", \"email\"]\n
You can also use
"},{"location":"ref/remote-cli/","title":"Controlling headscale with remote CLI","text":"allowed_domains
andallowed_users
to restrict the users who can authenticate.This documentation has the goal of showing a user how-to control a headscale instance from a remote machine with the
"},{"location":"ref/remote-cli/#prerequisite","title":"Prerequisite","text":"headscale
command line binary.- 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.
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:
"},{"location":"ref/remote-cli/#download-and-configure-headscale","title":"Download and configure headscale","text":"headscale apikeys expire --prefix \"<PREFIX>\"\n
-
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:
Minimal YAML configuration fileEnvironment variables config.yamlcli:\n address: <HEADSCALE_ADDRESS>:<PORT>\n api_key: <API_KEY_FROM_PREVIOUS_STEP>\n
export HEADSCALE_CLI_ADDRESS=\"<HEADSCALE_ADDRESS>:<PORT>\"\nexport HEADSCALE_CLI_API_KEY=\"<API_KEY_FROM_PREVIOUS_STEP>\"\n
Bug
Headscale currently requires at least an empty configuration file when environment variables are used to specify connection details. See issue 2193 for more information.
This instructs the
headscale
binary to connect to a remote instance at<HEADSCALE_ADDRESS>:<PORT>
, instead of connecting to the local instance. -
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.
It is possible to run the gRPC remote endpoint behind a reverse proxy, like Nginx, and have it run on the same port as headscale.
While this is not a supported feature, an example on how this can be set up on NixOS is shown here.
"},{"location":"ref/remote-cli/#troubleshooting","title":"Troubleshooting","text":"- Make sure you have the same headscale version on your server and workstation.
- Ensure that connections to the gRPC port are allowed.
- Verify that your TLS certificate is valid and trusted.
- If you don't have access to a trusted certificate (e.g. from Let's Encrypt), either:
- Add your self-signed certificate to the trust store of your OS or
- Disable certificate verification by either setting
cli.insecure: true
in the configuration file or by settingHEADSCALE_CLI_INSECURE=1
via an environment variable. We do not recommend to disable certificate validation.
Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the
config.yamltls_cert_path
andtls_cert_path
configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.tls_cert_path: \"\"\ntls_key_path: \"\"\n
The certificate should contain the full chain, else some clients, like the Tailscale Android client, will reject it.
"},{"location":"ref/tls/#lets-encrypt-acme","title":"Let's Encrypt / ACME","text":"To get a certificate automatically via Let's Encrypt, set
config.yamltls_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 theserver_url
configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured intls_letsencrypt_cache_dir
. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.
"},{"location":"ref/tls/#challenge-types","title":"Challenge types","text":"tls_letsencrypt_hostname: \"\"\ntls_letsencrypt_listen: \":http\"\ntls_letsencrypt_cache_dir: \".cache\"\ntls_letsencrypt_challenge_type: HTTP-01\n
Headscale only supports two values for
"},{"location":"ref/tls/#http-01","title":"HTTP-01","text":"tls_letsencrypt_challenge_type
:HTTP-01
(default) andTLS-ALPN-01
.For
HTTP-01
, headscale must be reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured inlisten_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
"},{"location":"ref/tls/#tls-alpn-01","title":"TLS-ALPN-01","text":"tls_letsencrypt_listen
to the appropriate value. This can be handy if you are running headscale as a non-root user (or can't runsetcap
). Keep in mind, however, that Let's Encrypt will only connect to port 80 for the validation callback, so if you changetls_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 intls_letsencrypt_listen
.For
"},{"location":"ref/tls/#technical-description","title":"Technical description","text":"TLS-ALPN-01
, headscale listens on the ip:port combination defined inlisten_addr
. Let's Encrypt will only connect to port 443 for the validation callback, so iflisten_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 inlisten_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.
- Or, check remotely from CLI using
openssl
:
"},{"location":"ref/tls/#log-output-from-the-autocert-library","title":"Log output from the autocert library","text":"$ openssl s_client -servername [hostname] -connect [hostname]:443 | openssl x509 -noout -dates\n(...)\nnotBefore=Feb 8 09:48:26 2024 GMT\nnotAfter=May 8 09:48:25 2024 GMT\n
As these log lines are from the autocert library, they are not strictly generated by headscale itself.
acme/autocert: missing server name\n
Likely caused by an incoming connection that does not specify a hostname, for example a
curl
request directly against the IP of the server, or an unexpected hostname.acme/autocert: host \"[foo]\" not configured in HostWhitelist\n
Similarly to the above, this likely indicates an invalid incoming request for an incorrect hostname, commonly just the IP itself.
The source code for autocert can be found here
"},{"location":"ref/integration/reverse-proxy/","title":"Running headscale behind a reverse proxy","text":"Community documentation
This page is not actively maintained by the headscale authors and is written by community members. It is not verified by headscale developers.
It might be outdated and it might miss necessary steps.
Running headscale behind a reverse proxy is useful when running multiple applications on the same server, and you want to reuse the same external IP and port - usually tcp/443 for HTTPS.
"},{"location":"ref/integration/reverse-proxy/#websockets","title":"WebSockets","text":"The reverse proxy MUST be configured to support WebSockets to communicate with Tailscale clients.
WebSockets support is also required when using the headscale embedded DERP server. In this case, you will also need to expose the UDP port used for STUN (by default, udp/3478). Please check our config-example.yaml.
"},{"location":"ref/integration/reverse-proxy/#cloudflare","title":"Cloudflare","text":"Running headscale behind a cloudflare proxy or cloudflare tunnel is not supported and will not work as Cloudflare does not support WebSocket POSTs as required by the Tailscale protocol. See this issue
"},{"location":"ref/integration/reverse-proxy/#tls","title":"TLS","text":"Headscale can be configured not to use TLS, leaving it to the reverse proxy to handle. Add the following configuration values to your headscale config file.
config.yaml
"},{"location":"ref/integration/reverse-proxy/#nginx","title":"nginx","text":"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.
nginx.conf<IP:PORT>
should be the IP address and port where headscale is running. In most cases, this will behttp://localhost:8080
.
"},{"location":"ref/integration/reverse-proxy/#istioenvoy","title":"istio/envoy","text":"map $http_upgrade $connection_upgrade {\n default upgrade;\n '' close;\n}\n\nserver {\n listen 80;\n listen [::]:80;\n\n listen 443 ssl http2;\n listen [::]:443 ssl http2;\n\n server_name <YOUR_SERVER_NAME>;\n\n ssl_certificate <PATH_TO_CERT>;\n ssl_certificate_key <PATH_CERT_KEY>;\n ssl_protocols TLSv1.2 TLSv1.3;\n\n location / {\n proxy_pass http://<IP:PORT>;\n proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection $connection_upgrade;\n proxy_set_header Host $server_name;\n proxy_redirect http:// https://;\n proxy_buffering off;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n add_header Strict-Transport-Security \"max-age=15552000; includeSubDomains\" always;\n }\n}\n
If you using Istio ingressgateway or Envoy as reverse proxy, there are some tips for you. If not set, you may see some debug log in proxy as below:
"},{"location":"ref/integration/reverse-proxy/#envoy","title":"Envoy","text":"Sending local reply with details upgrade_failed\n
You need to add a new upgrade_type named
"},{"location":"ref/integration/reverse-proxy/#istio","title":"Istio","text":"tailscale-control-protocol
. see detailsSame as envoy, we can use
EnvoyFilter
to add upgrade_type.
"},{"location":"ref/integration/reverse-proxy/#caddy","title":"Caddy","text":"apiVersion: networking.istio.io/v1alpha3\nkind: EnvoyFilter\nmetadata:\n name: headscale-behind-istio-ingress\n namespace: istio-system\nspec:\n configPatches:\n - applyTo: NETWORK_FILTER\n match:\n listener:\n filterChain:\n filter:\n name: envoy.filters.network.http_connection_manager\n patch:\n operation: MERGE\n value:\n typed_config:\n \"@type\": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager\n upgrade_configs:\n - upgrade_type: tailscale-control-protocol\n
The following Caddyfile is all that is necessary to use Caddy as a reverse proxy for headscale, in combination with the
Caddyfileconfig.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 belocalhost:8080
.<YOUR_SERVER_NAME> {\n reverse_proxy <IP:PORT>\n}\n
Caddy v2 will automatically provision a certificate for your domain/subdomain, force HTTPS, and proxy websockets - no further configuration is necessary.
For a slightly more complex configuration which utilizes Docker containers to manage Caddy, headscale, and Headscale-UI, Guru Computing's guide is an excellent reference.
"},{"location":"ref/integration/reverse-proxy/#apache","title":"Apache","text":"The following minimal Apache config will proxy traffic to the headscale instance on
apache.conf<IP:PORT>
. Note thatupgrade=any
is required as a parameter forProxyPass
so that WebSockets traffic whoseUpgrade
header value is not equal toWebSocket
(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":"<VirtualHost *:443>\n ServerName <YOUR_SERVER_NAME>\n\n ProxyPreserveHost On\n ProxyPass / http://<IP:PORT>/ upgrade=any\n\n SSLEngine On\n SSLCertificateFile <PATH_TO_CERT>\n SSLCertificateKeyFile <PATH_CERT_KEY>\n</VirtualHost>\n
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 adminsYou can ask for support on our Discord server in the \"web-interfaces\" channel.
"},{"location":"setup/requirements/","title":"Requirements","text":"Headscale should just work as long as the following requirements are met:
- A server with a public IP address for headscale. A dual-stack setup with a public IPv4 and a public IPv6 address is recommended.
- Headscale is served via HTTPS on port 4431.
- A reasonably modern Linux or BSD based operating system.
- A dedicated user account to run headscale.
- A little bit of command line knowledge to configure and operate headscale.
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 asheadscale.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
Update an existing headscale installation to a new version:
- Read the announcement on the GitHub releases page for the new version. It lists the changes of the release along with possible breaking changes.
- Create a backup of your database.
- Update headscale to the new version, preferably by following the same installation method.
- Compare and update the configuration file.
- Restart headscale.
Several Linux distributions and community members provide packages for headscale. Those packages may be used instead of the official releases provided by the headscale maintainers. Such packages offer improved integration for their targeted operating system and usually:
- setup a dedicated user account to run headscale
- provide a default configuration
- install headscale as system service
Community packages might be outdated
The packages mentioned on this page might be outdated or unmaintained. Use the official releases to get the current stable version or to test pre-releases.
"},{"location":"setup/install/community/#arch-linux","title":"Arch Linux","text":"Arch Linux offers a package for headscale, install via:
pacman -S headscale\n
The AUR package
"},{"location":"setup/install/community/#fedora-rhel-centos","title":"Fedora, RHEL, CentOS","text":"headscale-git
can be used to build the current development version.A third-party repository for various RPM based distributions is available at: https://copr.fedorainfracloud.org/coprs/jonathanspw/headscale/. The site provides detailed setup and installation instructions.
"},{"location":"setup/install/community/#nix-nixos","title":"Nix, NixOS","text":"A Nix package is available as:
"},{"location":"setup/install/community/#gentoo","title":"Gentoo","text":"headscale
. See the NixOS package site for installation details.emerge --ask net-vpn/headscale\n
Gentoo specific documentation is available here.
"},{"location":"setup/install/community/#openbsd","title":"OpenBSD","text":"Headscale is available in ports. The port installs headscale as system service with
rc.d
and provides usage instructions upon installation.
"},{"location":"setup/install/container/","title":"Running headscale in a container","text":"pkg_add headscale\n
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.sudo mkdir -p /etc/headscale\nsudo nano /etc/headscale/config.yaml\n
Alternatively, you can mount
/var/lib
and/var/run
from your host system by adding--volume $(pwd)/lib:/var/lib/headscale
and--volume $(pwd)/run:/var/run/headscale
in the next step. -
Start the headscale server while working in the host headscale directory:
docker run \\\n --name headscale \\\n --detach \\\n --volume $(pwd)/config:/etc/headscale/ \\\n --publish 127.0.0.1:8080:8080 \\\n --publish 127.0.0.1:9090:9090 \\\n headscale/headscale:<VERSION> \\\n serve\n
Note: use
0.0.0.0:8080:8080
instead of127.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
-
Verify headscale is running:
Follow the container logs:
docker logs --follow headscale\n
Verify running containers:
docker ps\n
Verify headscale is available:
curl http://127.0.0.1:9090/metrics\n
-
Create a user (tailnet):
docker exec -it headscale \\\n headscale users create myfirstuser\n
On a client machine, execute the
tailscale
login command:tailscale up --login-server YOUR_HEADSCALE_URL\n
To register a machine when running headscale in a container, take the headscale command and pass it to the container:
"},{"location":"setup/install/container/#register-machine-using-a-pre-authenticated-key","title":"Register machine using a pre authenticated key","text":"docker exec -it headscale \\\n headscale nodes register --user myfirstuser --key <YOUR_MACHINE_KEY>\n
Generate a key using the command line:
docker exec -it headscale \\\n headscale preauthkeys create --user myfirstuser --reusable --expiration 24h\n
This will return a pre-authenticated key that can be used to connect a node to headscale during the
tailscale
command:
"},{"location":"setup/install/container/#debugging-headscale-running-in-docker","title":"Debugging headscale running in Docker","text":"tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\n
The
"},{"location":"setup/install/container/#running-the-debug-docker-container","title":"Running the debug Docker container","text":"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 exampleheadscale/headscale:x.x.x-debug
.To run the debug Docker container, use the exact same commands as above, but replace
"},{"location":"setup/install/container/#executing-commands-in-the-debug-container","title":"Executing commands in the debug container","text":"headscale/headscale:x.x.x
withheadscale/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.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
"},{"location":"setup/install/official/","title":"Official releases","text":"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:
sudo nano /etc/headscale/config.yaml\n
-
Enable and start the headscale service:
sudo systemctl enable --now headscale\n
-
Verify that headscale is running as intended:
sudo systemctl status headscale\n
Advanced
This installation method is considered advanced as one needs to take care of the headscale user and the systemd service themselves. If possible, use the DEB packages or a community package instead.
This section describes the installation of headscale according to the Requirements and assumptions. Headscale is run by a dedicated user and the service itself is managed by systemd.
-
Download the latest
headscale
binary from GitHub's release page:sudo wget --output-document=/usr/local/bin/headscale \\\nhttps://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>\n
-
Make
headscale
executable:sudo chmod +x /usr/local/bin/headscale\n
-
Add a dedicated user to run headscale:
sudo useradd \\\n --create-home \\\n --home-dir /var/lib/headscale/ \\\n --system \\\n --user-group \\\n --shell /usr/sbin/nologin \\\n headscale\n
-
Download the example configuration for your chosen version and save it as:
/etc/headscale/config.yaml
. Adjust the configuration to suit your local environment. See Configuration for details.sudo mkdir -p /etc/headscale\nsudo nano /etc/headscale/config.yaml\n
-
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
config.yaml/etc/headscale/config.yaml
, override the defaultheadscale
unix socket with a path that is writable by theheadscale
user or group: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
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":"
"},{"location":"setup/install/source/#install-from-source-via-cross-compile","title":"Install from source via cross compile","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":"usage/getting-started/","title":"Getting started","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
NativeContainerheadscale
command line tool provides built-in help. To show available commands along with their arguments and options, run:# Show help\nheadscale help\n\n# Show help for a specific command\nheadscale <COMMAND> --help\n
"},{"location":"usage/getting-started/#manage-users","title":"Manage users","text":"# 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
"},{"location":"usage/getting-started/#create-a-user","title":"Create a user","text":"NativeContainerheadscale users
command. Invoke the built-in help for more information:headscale users --help
.headscale users create <USER>\n
"},{"location":"usage/getting-started/#list-existing-users","title":"List existing users","text":"NativeContainerdocker exec -it headscale \\\n headscale users create <USER>\n
headscale users list\n
"},{"location":"usage/getting-started/#register-a-node","title":"Register a node","text":"docker exec -it headscale \\\n headscale users list\n
One has to register a node first to use headscale as coordination with Tailscale. The following examples work for the Tailscale client on Linux/BSD operating systems. Alternatively, follow the instructions to connect Android, Apple or Windows devices.
"},{"location":"usage/getting-started/#normal-interactive-login","title":"Normal, interactive login","text":"On a client machine, run the
tailscale up
command and provide the FQDN of your headscale instance as argument:tailscale up --login-server <YOUR_HEADSCALE_URL>\n
Usually, a browser window with further instructions is opened and contains the value for
NativeContainer<YOUR_MACHINE_KEY>
. Approve and register the node on your headscale server:headscale nodes register --user <USER> --key <YOUR_MACHINE_KEY>\n
"},{"location":"usage/getting-started/#using-a-preauthkey","title":"Using a preauthkey","text":"docker exec -it headscale \\\n headscale nodes register --user <USER> --key <YOUR_MACHINE_KEY>\n
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
NativeContainerheadscale preauthkeys --help
for other options):headscale preauthkeys create --user <USER>\n
docker exec -it headscale \\\n headscale preauthkeys create --user <USER>\n
The command returns the preauthkey on success which is used to connect a node to the headscale instance via the
tailscale up
command:
"},{"location":"usage/connect/android/","title":"Connecting an Android client","text":"tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\n
This documentation has the goal of showing how a user can use the official Android Tailscale client with headscale.
"},{"location":"usage/connect/android/#installation","title":"Installation","text":"Install the official Tailscale Android client from the Google Play Store or F-Droid.
"},{"location":"usage/connect/android/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"- Open the app and select the settings menu in the upper-right corner
- Tap on
Accounts
- In the kebab menu icon (three dots) in the upper-right corner select
Use an alternate server
- Enter your server URL (e.g
https://headscale.example.com
) and follow the instructions
This documentation has the goal of showing how a user can use the official iOS and macOS Tailscale clients with headscale.
Instructions on your headscale instance
An endpoint with information on how to connect your Apple device is also available at
"},{"location":"usage/connect/apple/#ios","title":"iOS","text":""},{"location":"usage/connect/apple/#installation","title":"Installation","text":"/apple
on your running instance.Install the official Tailscale iOS client from the App Store.
"},{"location":"usage/connect/apple/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"- Open Tailscale and make sure you are not logged in to any account
- Open Settings on the iOS device
- Scroll down to the
third party apps
section, underGame Center
orTV Provider
- Find Tailscale and select it
- If the iOS device was previously logged into Tailscale, switch the
Reset Keychain
toggle toon
- Enter the URL of your headscale instance (e.g
https://headscale.example.com
) underAlternate Coordination Server URL
- Restart the app by closing it from the iOS app switcher, open the app and select the regular sign in option (non-SSO). It should open up to the headscale authentication page.
- Enter your credentials and log in. Headscale should now be working on your iOS device.
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
):
"},{"location":"usage/connect/apple/#gui","title":"GUI","text":"tailscale login --login-server <YOUR_HEADSCALE_URL>\n
- Option + Click the Tailscale icon in the menu and hover over the Debug menu
- Under
Custom Login Server
, selectAdd Account...
- Enter the URL of your headscale instance (e.g
https://headscale.example.com
) and pressAdd Account
- Follow the login procedure in the browser
Install the official Tailscale tvOS client from the App Store.
Danger
Don't open the Tailscale App after installation!
"},{"location":"usage/connect/apple/#configuring-the-headscale-url_2","title":"Configuring the headscale URL","text":"- Open Settings (the Apple tvOS settings) > Apps > Tailscale
- Under
ALTERNATE COORDINATION SERVER URL
, selectURL
- Enter the URL of your headscale instance (e.g
https://headscale.example.com
) and pressOK
- Return to the tvOS Home screen
- Open Tailscale
- Click the button
Install VPN configuration
and confirm the appearing popup by clicking theAllow
button - Scan the QR code and follow the login procedure
This documentation has the goal of showing how a user can use the official Windows Tailscale client with headscale.
Instructions on your headscale instance
An endpoint with information on how to connect your Windows device is also available at
"},{"location":"usage/connect/windows/#installation","title":"Installation","text":"/windows
on your running instance.Download the Official Windows Client and install it.
"},{"location":"usage/connect/windows/#configuring-the-headscale-url","title":"Configuring the headscale URL","text":"Open a Command Prompt or Powershell and use Tailscale's login command to connect with your headscale instance (e.g
https://headscale.example.com
):tailscale login --login-server <YOUR_HEADSCALE_URL>\n
Follow the instructions in the opened browser window to finish the configuration.
"},{"location":"usage/connect/windows/#troubleshooting","title":"Troubleshooting","text":""},{"location":"usage/connect/windows/#unattended-mode","title":"Unattended mode","text":"By default, Tailscale's Windows client is only running when the user is logged in. If you want to keep Tailscale running all the time, please enable \"Unattended mode\":
- Click on the Tailscale tray icon and select
Preferences
- Enable
Run unattended
- Confirm the \"Unattended mode\" message
See also Keep Tailscale running when I'm not logged in to my computer
"},{"location":"usage/connect/windows/#failing-node-registration","title":"Failing node registration","text":"If you are seeing repeated messages like:
[GIN] 2022/02/10 - 16:39:34 | 200 | 1.105306ms | 127.0.0.1 | POST \"/machine/redacted\"\n
in your headscale output, turn on
DEBUG
logging and look for:2022-02-11T00:59:29Z DBG Machine registration has expired. Sending a authurl to register machine=redacted\n
This typically means that the registry keys above was not set appropriately.
To reset and try again, it is important to do the following:
- 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.
Build from source - Headscale HeadscaleBuild from sourceBuild from source¶
Community documentation
This page is not actively maintained by the headscale authors and is written by community members. It is not verified by headscale developers.
It might be outdated and it might miss necessary steps.
Headscale can be built from source using the latest version of Go and Buf (Protobuf generator). See the Contributing section in the GitHub README for more information.
OpenBSD¶
Install from source¶
# Install prerequistes +
Build from source - Headscale HeadscaleBuild from sourceBuild from source¶
Community documentation
This page is not actively maintained by the headscale authors and is written by community members. It is not verified by headscale developers.
It might be outdated and it might miss necessary steps.
Headscale can be built from source using the latest version of Go and Buf (Protobuf generator). See the Contributing section in the GitHub README for more information.
OpenBSD¶
Install from source¶
# Install prerequisites pkg_add go git clone https://github.com/juanfont/headscale.git @@ -19,7 +19,7 @@ # copy it to /usr/local/sbin cp headscale /usr/local/sbin -
Install from source via cross compile¶
Install from source via cross compile¶
# Install prerequisites # 1. go v1.20+: headscale newer than 0.21 needs go 1.20+ to compile # 2. gmake: Makefile in the headscale repo is written in GNU make syntax diff --git a/development/sitemap.xml b/development/sitemap.xml index 0735fb69..86af0a48 100644 --- a/development/sitemap.xml +++ b/development/sitemap.xml @@ -2,114 +2,114 @@
https://juanfont.github.io/headscale/development/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/about/clients/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/about/contributing/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/about/faq/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/about/features/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/about/help/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/about/releases/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/about/sponsor/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/ref/acls/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/ref/configuration/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/ref/dns/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/ref/exit-node/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/ref/oidc/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/ref/remote-cli/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/ref/tls/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/ref/integration/reverse-proxy/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/ref/integration/tools/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/ref/integration/web-ui/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/setup/requirements/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/setup/upgrade/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/setup/install/community/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/setup/install/container/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/setup/install/official/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/setup/install/source/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/usage/getting-started/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/usage/connect/android/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/usage/connect/apple/ -2024-12-22 +2025-01-09 https://juanfont.github.io/headscale/development/usage/connect/windows/ -2024-12-22 +2025-01-09
- Use the