Frequently Asked Questions¶
What is the design goal of headscale?¶
Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. It implements a narrow scope, a single Tailscale network (tailnet), suitable for a personal use, or a small open-source organisation.
How can I contribute?¶
Headscale is "Open Source, acknowledged contribution", this means that any contribution will have to be discussed with the Maintainers before being submitted.
Please see Contributing for more information.
Why is 'acknowledged contribution' the chosen model?¶
Both maintainers have full-time jobs and families, and we want to avoid burnout. We also want to avoid frustration from contributors when their PRs are not accepted.
We are more than happy to exchange emails, or to have dedicated calls before a PR is submitted.
When/Why is Feature X going to be implemented?¶
We use GitHub Milestones to plan for upcoming Headscale releases. Have a look at our current plan to get an idea when a specific feature is about to be implemented. The release plan is subject to change at any time.
If you're interested in contributing, please post a feature request about it. Please be aware that there are a number of reasons why we might not accept specific contributions:
- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
- Given that we are reverse-engineering Tailscale to satisfy our own curiosity, we might be interested in implementing the feature ourselves.
- You are not sending unit and integration tests with it.
Do you support Y method of deploying headscale?¶
We currently support deploying headscale using our binaries and the DEB packages. Visit our installation guide using official releases for more information.
In addition to that, you may use packages provided by the community or from distributions. Learn more in the installation guide using community packages.
For convenience, we also build container images with headscale. But please be aware that we don't officially support deploying headscale using Docker. On our Discord server we have a "docker-issues" channel where you can ask for Docker-specific help to the community.
What is the recommended update path? Can I skip multiple versions while updating?¶
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation. Its required to update from one stable version to the next (e.g. 0.26.0 → 0.27.1 → 0.28.0) without skipping minor versions in between. You should always pick the latest available patch release.
Be sure to check the changelog for version specific upgrade instructions and breaking changes.
Scaling / How many clients does Headscale support?¶
It depends. As often stated, Headscale is not enterprise software and our focus is homelabbers and self-hosters. Of course, we do not prevent people from using it in a commercial/professional setting and often get questions about scaling.
Please note that when Headscale is developed, performance is not part of the consideration as the main audience is considered to be users with a modest amount of devices. We focus on correctness and feature parity with Tailscale SaaS over time.
To understand if you might be able to use Headscale for your use case, I will describe two scenarios in an effort to explain what is the central bottleneck of Headscale:
-
An environment with 1000 servers
-
they rarely "move" (change their endpoints)
-
new nodes are added rarely
-
An environment with 80 laptops/phones (end user devices)
-
nodes move often, e.g. switching from home to office
Headscale calculates a map of all nodes that need to talk to each other, creating this "world map" requires a lot of CPU time. When an event that requires changes to this map happens, the whole "world" is recalculated, and a new "world map" is created for every node in the network.
This means that under certain conditions, Headscale can likely handle 100s of devices (maybe more), if there is little to no change happening in the network. For example, in Scenario 1, the process of computing the world map is extremely demanding due to the size of the network, but when the map has been created and the nodes are not changing, the Headscale instance will likely return to a very low resource usage until the next time there is an event requiring the new map.
In the case of Scenario 2, the process of computing the world map is less demanding due to the smaller size of the network, however, the type of nodes will likely change frequently, which would lead to a constant resource usage.
Headscale will start to struggle when the two scenarios overlap, e.g. many nodes with frequent changes will cause the resource usage to remain constantly high. In the worst case scenario, the queue of nodes waiting for their map will grow to a point where Headscale never will be able to catch up, and nodes will never learn about the current state of the world.
We expect that the performance will improve over time as we improve the code base, but it is not a focus. In general, we will never make the tradeoff to make things faster on the cost of less maintainable or readable code. We are a small team and have to optimise for maintainability.
Which database should I use?¶
We recommend the use of SQLite as database for headscale:
- SQLite is simple to setup and easy to use
- It scales well for all of headscale's use cases
- Development and testing happens primarily on SQLite
- PostgreSQL is still supported, but is considered to be in "maintenance mode"
The headscale project itself does not provide a tool to migrate from PostgreSQL to SQLite. Please have a look at the related tools documentation for migration tooling provided by the community.
The choice of database has little to no impact on the performance of the server, see Scaling / How many clients does Headscale support? for understanding how Headscale spends its resources.
Why is my reverse proxy not working with headscale?¶
We don't know. We don't use reverse proxies with headscale ourselves, so we don't have any experience with them. We have community documentation on how to configure various reverse proxies, and a dedicated "reverse-proxy-issues" channel on our Discord server where you can ask for help to the community.
Can I use headscale and tailscale on the same machine?¶
Running headscale on a machine that is also in the tailnet can cause problems with subnet routers, traffic relay nodes, and MagicDNS. It might work, but it is not supported.
Why do two nodes see each other in their status, even if an ACL allows traffic only in one direction?¶
A frequent use case is to allow traffic only from one node to another, but not the other way around. For example, the workstation of an administrator should be able to connect to all nodes but the nodes themselves shouldn't be able to connect back to the administrator's node. Why do all nodes see the administrator's workstation in the output of tailscale status?
This is essentially how Tailscale works. If traffic is allowed to flow in one direction, then both nodes see each other in their output of tailscale status. Traffic is still filtered according to the ACL, with the exception of tailscale ping which is always allowed in either direction.
See also https://tailscale.com/kb/1087/device-visibility.
My policy is stored in the database and Headscale refuses to start due to an invalid policy. How can I recover?¶
Headscale checks if the policy is valid during startup and refuses to start if it detects an error. The error message indicates which part of the policy is invalid. Follow these steps to fix your policy:
- Dump the policy to a file:
headscale policy get --bypass-grpc-and-access-database-directly > policy.json - Edit and fixup
policy.json. Use the commandheadscale policy check --file policy.jsonto validate the policy. - Load the modified policy:
headscale policy set --bypass-grpc-and-access-database-directly --file policy.json - Start Headscale as usual.
Full server configuration required
The above commands to get/set the policy require a complete server configuration file including database settings. A minimal config to control Headscale via remote CLI is not sufficient. You may use headscale -c /path/to/config.yaml to specify the path to an alternative configuration file.
How can I migrate back to the recommended IP prefixes?¶
Tailscale only supports the IP prefixes 100.64.0.0/10 and fd7a:115c:a1e0::/48 or smaller subnets thereof. The following steps can be used to migrate from unsupported IP prefixes back to the supported and recommended ones.
Backup and test in a demo environment required
The commands below update the IP addresses of all nodes in your tailnet and this might have a severe impact in your specific environment. At a minimum:
- Create a backup of your database
- Test the commands below in a representive demo environment. This allows to catch subsequent connectivity errors early and see how the tailnet behaves in your specific environment.
- Stop Headscale
- Restore the default prefixes in the configuration file:
prefixes: +FAQ - Headscale HeadscaleFAQFrequently Asked Questions¶
What is the design goal of headscale?¶
Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. It implements a narrow scope, a single Tailscale network (tailnet), suitable for a personal use, or a small open-source organisation.
How can I contribute?¶
Headscale is "Open Source, acknowledged contribution", this means that any contribution will have to be discussed with the Maintainers before being submitted.
Please see Contributing for more information.
Why is 'acknowledged contribution' the chosen model?¶
Both maintainers have full-time jobs and families, and we want to avoid burnout. We also want to avoid frustration from contributors when their PRs are not accepted.
We are more than happy to exchange emails, or to have dedicated calls before a PR is submitted.
When/Why is Feature X going to be implemented?¶
We use GitHub Milestones to plan for upcoming Headscale releases. Have a look at our current plan to get an idea when a specific feature is about to be implemented. The release plan is subject to change at any time.
If you're interested in contributing, please post a feature request about it. Please be aware that there are a number of reasons why we might not accept specific contributions:
- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
- Given that we are reverse-engineering Tailscale to satisfy our own curiosity, we might be interested in implementing the feature ourselves.
- You are not sending unit and integration tests with it.
Do you support Y method of deploying headscale?¶
We currently support deploying headscale using our binaries and the DEB packages. Visit our installation guide using official releases for more information.
In addition to that, you may use packages provided by the community or from distributions. Learn more in the installation guide using community packages.
For convenience, we also build container images with headscale. But please be aware that we don't officially support deploying headscale using Docker. On our Discord server we have a "docker-issues" channel where you can ask for Docker-specific help to the community.
What is the recommended update path? Can I skip multiple versions while updating?¶
Please follow the steps outlined in the upgrade guide to update your existing Headscale installation. Its required to update from one stable version to the next (e.g. 0.26.0 → 0.27.1 → 0.28.0) without skipping minor versions in between. You should always pick the latest available patch release.
Be sure to check the changelog for version specific upgrade instructions and breaking changes.
Scaling / How many clients does Headscale support?¶
It depends. As often stated, Headscale is not enterprise software and our focus is homelabbers and self-hosters. Of course, we do not prevent people from using it in a commercial/professional setting and often get questions about scaling.
Please note that when Headscale is developed, performance is not part of the consideration as the main audience is considered to be users with a modest amount of devices. We focus on correctness and feature parity with Tailscale SaaS over time.
To understand if you might be able to use Headscale for your use case, I will describe two scenarios in an effort to explain what is the central bottleneck of Headscale:
-
An environment with 1000 servers
- they rarely "move" (change their endpoints)
- new nodes are added rarely
-
An environment with 80 laptops/phones (end user devices)
- nodes move often, e.g. switching from home to office
Headscale calculates a map of all nodes that need to talk to each other, creating this "world map" requires a lot of CPU time. When an event that requires changes to this map happens, the whole "world" is recalculated, and a new "world map" is created for every node in the network.
This means that under certain conditions, Headscale can likely handle 100s of devices (maybe more), if there is little to no change happening in the network. For example, in Scenario 1, the process of computing the world map is extremely demanding due to the size of the network, but when the map has been created and the nodes are not changing, the Headscale instance will likely return to a very low resource usage until the next time there is an event requiring the new map.
In the case of Scenario 2, the process of computing the world map is less demanding due to the smaller size of the network, however, the type of nodes will likely change frequently, which would lead to a constant resource usage.
Headscale will start to struggle when the two scenarios overlap, e.g. many nodes with frequent changes will cause the resource usage to remain constantly high. In the worst case scenario, the queue of nodes waiting for their map will grow to a point where Headscale never will be able to catch up, and nodes will never learn about the current state of the world.
We expect that the performance will improve over time as we improve the code base, but it is not a focus. In general, we will never make the tradeoff to make things faster on the cost of less maintainable or readable code. We are a small team and have to optimise for maintainability.
Which database should I use?¶
We recommend the use of SQLite as database for headscale:
- SQLite is simple to setup and easy to use
- It scales well for all of headscale's use cases
- Development and testing happens primarily on SQLite
- PostgreSQL is still supported, but is considered to be in "maintenance mode"
The headscale project itself does not provide a tool to migrate from PostgreSQL to SQLite. Please have a look at the related tools documentation for migration tooling provided by the community.
The choice of database has little to no impact on the performance of the server, see Scaling / How many clients does Headscale support? for understanding how Headscale spends its resources.
Why is my reverse proxy not working with headscale?¶
We don't know. We don't use reverse proxies with headscale ourselves, so we don't have any experience with them. We have community documentation on how to configure various reverse proxies, and a dedicated "reverse-proxy-issues" channel on our Discord server where you can ask for help to the community.
Can I use headscale and tailscale on the same machine?¶
Running headscale on a machine that is also in the tailnet can cause problems with subnet routers, traffic relay nodes, and MagicDNS. It might work, but it is not supported.
Why do two nodes see each other in their status, even if an ACL allows traffic only in one direction?¶
A frequent use case is to allow traffic only from one node to another, but not the other way around. For example, the workstation of an administrator should be able to connect to all nodes but the nodes themselves shouldn't be able to connect back to the administrator's node. Why do all nodes see the administrator's workstation in the output of
tailscale status?This is essentially how Tailscale works. If traffic is allowed to flow in one direction, then both nodes see each other in their output of
tailscale status. Traffic is still filtered according to the ACL, with the exception oftailscale pingwhich is always allowed in either direction.See also https://tailscale.com/kb/1087/device-visibility.
My policy is stored in the database and Headscale refuses to start due to an invalid policy. How can I recover?¶
Headscale checks if the policy is valid during startup and refuses to start if it detects an error. The error message indicates which part of the policy is invalid. Follow these steps to fix your policy:
- Dump the policy to a file:
headscale policy get --bypass-grpc-and-access-database-directly > policy.json - Edit and fixup
policy.json. Use the commandheadscale policy check --file policy.jsonto validate the policy. - Load the modified policy:
headscale policy set --bypass-grpc-and-access-database-directly --file policy.json - Start Headscale as usual.
Full server configuration required
The above commands to get/set the policy require a complete server configuration file including database settings. A minimal config to control Headscale via remote CLI is not sufficient. You may use
headscale -c /path/to/config.yamlto specify the path to an alternative configuration file.How can I migrate back to the recommended IP prefixes?¶
Tailscale only supports the IP prefixes
100.64.0.0/10andfd7a:115c:a1e0::/48or smaller subnets thereof. The following steps can be used to migrate from unsupported IP prefixes back to the supported and recommended ones.Backup and test in a demo environment required
The commands below update the IP addresses of all nodes in your tailnet and this might have a severe impact in your specific environment. At a minimum:
- Create a backup of your database
- Test the commands below in a representive demo environment. This allows to catch subsequent connectivity errors early and see how the tailnet behaves in your specific environment.
- Stop Headscale
- Restore the default prefixes in the configuration file:
- Update the
nodes.ipv4andnodes.ipv6columns in the database and assign each node a unique IPv4 and IPv6 address. The following SQL statement assigns IP addresses based on the node ID:UPDATE nodes diff --git a/development/search/search_index.json b/development/search/search_index.json index 9b38e300..adfef037 100644 --- a/development/search/search_index.json +++ b/development/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-,:!=\\[\\]()\"`/]+|\\.(?!\\d)|&[lg]t;|(?!\\b)(?=[A-Z][a-z])","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Welcome to headscale","text":"Headscale is an open source, self-hosted implementation of the Tailscale control server.
This page contains the documentation for the latest version of headscale. Please also check our FAQ.
Join our Discord server for a chat and community support.
"},{"location":"#design-goal","title":"Design goal","text":"Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. It implements a narrow scope, a single Tailscale network (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/windowson your headscale for more information) Android Yes (see docs for more information) macOS Yes (see docs and/appleon your headscale for more information) iOS Yes (see docs and/appleon your headscale for more information) tvOS Yes (see docs and/appleon 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 Tailscale network (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 use GitHub Milestones to plan for upcoming Headscale releases. Have a look at our current plan to get an idea when a specific feature is about to be implemented. The release plan is subject to change at any time.
If you're interested in contributing, please post a feature request about it. Please be aware that there are a number of reasons why we might not accept specific contributions:
- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
- Given that we are reverse-engineering Tailscale to satisfy our own curiosity, we might be interested in implementing the feature ourselves.
- You are not sending unit and integration tests with it.
We currently support deploying headscale using our binaries and the DEB packages. Visit our installation guide using official releases for more information.
In addition to that, you may use packages provided by the community or from distributions. Learn more in the installation guide using community packages.
For convenience, we also build container images with headscale. But please be aware that we don't officially support deploying headscale using Docker. On our Discord server we have a \"docker-issues\" channel where you can ask for Docker-specific help to the community.
"},{"location":"about/faq/#what-is-the-recommended-update-path-can-i-skip-multiple-versions-while-updating","title":"What is the recommended update path? Can I skip multiple versions while updating?","text":"Please follow the steps outlined in the upgrade guide to update your existing Headscale installation. Its required to update from one stable version to the next (e.g. 0.26.0 \u2192 0.27.1 \u2192 0.28.0) without skipping minor versions in between. You should always pick the latest available patch release.
Be sure to check the changelog for version specific upgrade instructions and breaking changes.
"},{"location":"about/faq/#scaling-how-many-clients-does-headscale-support","title":"Scaling / How many clients does Headscale support?","text":"It depends. As often stated, Headscale is not enterprise software and our focus is homelabbers and self-hosters. Of course, we do not prevent people from using it in a commercial/professional setting and often get questions about scaling.
Please note that when Headscale is developed, performance is not part of the consideration as the main audience is considered to be users with a modest amount of devices. We focus on correctness and feature parity with Tailscale SaaS over time.
To understand if you might be able to use Headscale for your use case, I will describe two scenarios in an effort to explain what is the central bottleneck of Headscale:
-
An environment with 1000 servers
-
they rarely \"move\" (change their endpoints)
-
new nodes are added rarely
-
An environment with 80 laptops/phones (end user devices)
-
nodes move often, e.g. switching from home to office
Headscale calculates a map of all nodes that need to talk to each other, creating this \"world map\" requires a lot of CPU time. When an event that requires changes to this map happens, the whole \"world\" is recalculated, and a new \"world map\" is created for every node in the network.
This means that under certain conditions, Headscale can likely handle 100s of devices (maybe more), if there is little to no change happening in the network. For example, in Scenario 1, the process of computing the world map is extremely demanding due to the size of the network, but when the map has been created and the nodes are not changing, the Headscale instance will likely return to a very low resource usage until the next time there is an event requiring the new map.
In the case of Scenario 2, the process of computing the world map is less demanding due to the smaller size of the network, however, the type of nodes will likely change frequently, which would lead to a constant resource usage.
Headscale will start to struggle when the two scenarios overlap, e.g. many nodes with frequent changes will cause the resource usage to remain constantly high. In the worst case scenario, the queue of nodes waiting for their map will grow to a point where Headscale never will be able to catch up, and nodes will never learn about the current state of the world.
We expect that the performance will improve over time as we improve the code base, but it is not a focus. In general, we will never make the tradeoff to make things faster on the cost of less maintainable or readable code. We are a small team and have to optimise for maintainability.
"},{"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 use cases
- Development and testing happens primarily on SQLite
- PostgreSQL is still supported, but is considered to be in \"maintenance mode\"
The headscale project itself does not provide a tool to migrate from PostgreSQL to SQLite. Please have a look at the related tools documentation for migration tooling provided by the community.
The choice of database has little to no impact on the performance of the server, see Scaling / How many clients does Headscale support? for understanding how Headscale spends its resources.
"},{"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/faq/#why-do-two-nodes-see-each-other-in-their-status-even-if-an-acl-allows-traffic-only-in-one-direction","title":"Why do two nodes see each other in their status, even if an ACL allows traffic only in one direction?","text":"A frequent use case is to allow traffic only from one node to another, but not the other way around. For example, the workstation of an administrator should be able to connect to all nodes but the nodes themselves shouldn't be able to connect back to the administrator's node. Why do all nodes see the administrator's workstation in the output of
tailscale status?This is essentially how Tailscale works. If traffic is allowed to flow in one direction, then both nodes see each other in their output of
tailscale status. Traffic is still filtered according to the ACL, with the exception oftailscale pingwhich is always allowed in either direction.See also https://tailscale.com/kb/1087/device-visibility.
"},{"location":"about/faq/#my-policy-is-stored-in-the-database-and-headscale-refuses-to-start-due-to-an-invalid-policy-how-can-i-recover","title":"My policy is stored in the database and Headscale refuses to start due to an invalid policy. How can I recover?","text":"Headscale checks if the policy is valid during startup and refuses to start if it detects an error. The error message indicates which part of the policy is invalid. Follow these steps to fix your policy:
- Dump the policy to a file:
headscale policy get --bypass-grpc-and-access-database-directly > policy.json - Edit and fixup
policy.json. Use the commandheadscale policy check --file policy.jsonto validate the policy. - Load the modified policy:
headscale policy set --bypass-grpc-and-access-database-directly --file policy.json - Start Headscale as usual.
Full server configuration required
The above commands to get/set the policy require a complete server configuration file including database settings. A minimal config to control Headscale via remote CLI is not sufficient. You may use
"},{"location":"about/faq/#how-can-i-migrate-back-to-the-recommended-ip-prefixes","title":"How can I migrate back to the recommended IP prefixes?","text":"headscale -c /path/to/config.yamlto specify the path to an alternative configuration file.Tailscale only supports the IP prefixes
100.64.0.0/10andfd7a:115c:a1e0::/48or smaller subnets thereof. The following steps can be used to migrate from unsupported IP prefixes back to the supported and recommended ones.Backup and test in a demo environment required
The commands below update the IP addresses of all nodes in your tailnet and this might have a severe impact in your specific environment. At a minimum:
- Create a backup of your database
- Test the commands below in a representive demo environment. This allows to catch subsequent connectivity errors early and see how the tailnet behaves in your specific environment.
- Stop Headscale
- Restore the default prefixes in the configuration file:
prefixes:\n v4: 100.64.0.0/10\n v6: fd7a:115c:a1e0::/48\n - Update the
nodes.ipv4andnodes.ipv6columns in the database and assign each node a unique IPv4 and IPv6 address. The following SQL statement assigns IP addresses based on the node ID:UPDATE nodes\nSET ipv4=concat('100.64.', id/256, '.', id%256),\n ipv6=concat('fd7a:115c:a1e0::', format('%x', id));\n - Update the policy to reflect the IP address changes (if any)
- Start Headscale
Nodes should reconnect within a few seconds and pickup their newly assigned IP addresses.
"},{"location":"about/faq/#how-can-i-avoid-to-send-logs-to-tailscale-inc","title":"How can I avoid to send logs to Tailscale Inc?","text":"A Tailscale client collects logs about its operation and connection attempts with other clients and sends them to a central log service operated by Tailscale Inc.
Headscale, by default, instructs clients to disable log submission to the central log service. This configuration is applied by a client once it successfully connected with Headscale. See the configuration option
logtail.enabledin the configuration file for details.Alternatively, logging can also be disabled on the client side. This is independent of Headscale and opting out of client logging disables log submission early during client startup. The configuration is operating system specific and is usually achieved by setting the environment variable
"},{"location":"about/features/","title":"Features","text":"TS_NO_LOGS_NO_SUPPORT=trueor by passing the flag--no-logs-no-supporttotailscaled. See https://tailscale.com/kb/1011/log-mesh-traffic#opting-out-of-client-logging for details.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
- Web authentication
- Pre authenticated key
- DNS
- MagicDNS
- Global and restricted nameservers (split DNS)
- search domains
- Extra DNS records (Headscale only)
- Taildrop (File Sharing)
- Tags
- Routes
- Subnet routers
- Exit nodes
- Dual stack (IPv4 and IPv6)
- Ephemeral nodes
- Embedded DERP server
- Access control lists (GitHub label \"policy\")
- ACL management via API
- Some Autogroups, currently:
autogroup:internet,autogroup:nonroot,autogroup:member,autogroup:tagged,autogroup:self - Auto approvers for subnet routers and exit nodes
- Tailscale SSH
- Node registration using Single-Sign-On (OpenID Connect) (GitHub label \"OIDC\")
- Basic registration
- Update user profile from identity provider
- OIDC groups cannot be used in ACLs
- Funnel (#1040)
- Serve (#1234)
- Network flow logs (#1687)
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 and GitHub Container Registry.
An Atom/RSS feed of headscale releases is available here.
See the \"announcements\" channel on our Discord server for news about headscale.
"},{"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/#acl-setup","title":"ACL Setup","text":"To enable and configure ACLs in Headscale, you need to specify the path to your ACL policy file in the
policy.pathkey inconfig.yaml.Your ACL policy file must be formatted using huJSON.
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 (
"},{"location":"ref/acls/#simple-examples","title":"Simple Examples","text":"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.-
Allow All: If you define an ACL file but completely omit the
\"acls\"field from its content, Headscale will default to an \"allow all\" policy. This means all devices connected to your tailnet will be able to communicate freely with each other.{}\n -
Deny All: To prevent all communication within your tailnet, you can include an empty array for the
\"acls\"field in your policy file.{\n \"acls\": []\n}\n
Let's build a more complex 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
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.Here are the ACL's to implement the same permissions as above:
acl.json
"},{"location":"ref/acls/#autogroups","title":"Autogroups","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.\n {\n \"action\": \"accept\",\n \"src\": [\"group:dev\"],\n \"dst\": [\"10.20.0.0/16:443,5432\"]\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 // Allow users to access their own devices using autogroup:self (see below for more details about performance impact)\n {\n \"action\": \"accept\",\n \"src\": [\"autogroup:member\"],\n \"dst\": [\"autogroup:self:*\"]\n }\n ]\n}\nHeadscale supports several autogroups that automatically include users, destinations, or devices with specific properties. Autogroups provide a convenient way to write ACL rules without manually listing individual users or devices.
"},{"location":"ref/acls/#autogroupinternet","title":"autogroup:internet","text":"Allows access to the internet through exit nodes. Can only be used in ACL destinations.
"},{"location":"ref/acls/#autogroupmember","title":"{\n \"action\": \"accept\",\n \"src\": [\"group:users\"],\n \"dst\": [\"autogroup:internet:*\"]\n}\nautogroup:member","text":"Includes all personal (untagged) devices.
"},{"location":"ref/acls/#autogrouptagged","title":"{\n \"action\": \"accept\",\n \"src\": [\"autogroup:member\"],\n \"dst\": [\"tag:prod-app-servers:80,443\"]\n}\nautogroup:tagged","text":"Includes all devices that have at least one tag.
"},{"location":"ref/acls/#autogroupself","title":"{\n \"action\": \"accept\",\n \"src\": [\"autogroup:tagged\"],\n \"dst\": [\"tag:monitoring:9090\"]\n}\nautogroup:self","text":"The current implementation of
autogroup:selfis inefficientIncludes devices where the same user is authenticated on both the source and destination. Does not include tagged devices. Can only be used in ACL destinations.
Using{\n \"action\": \"accept\",\n \"src\": [\"autogroup:member\"],\n \"dst\": [\"autogroup:self:*\"]\n}\nautogroup:selfmay cause performance degradation on the Headscale coordinator server in large deployments, as filter rules must be compiled per-node rather than globally and the current implementation is not very efficient.If you experience performance issues, consider using more specific ACL rules or limiting the use of
autogroup:self.
"},{"location":"ref/acls/#autogroupnonroot","title":"{\n // The following rules allow internal users to communicate with their\n // own nodes in case autogroup:self is causing performance issues.\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}\nautogroup:nonroot","text":"Used in Tailscale SSH rules to allow access to any user except root. Can only be used in the
usersfield of SSH rules.
"},{"location":"ref/api/","title":"API","text":"{\n \"action\": \"accept\",\n \"src\": [\"autogroup:member\"],\n \"dst\": [\"autogroup:self\"],\n \"users\": [\"autogroup:nonroot\"]\n}\nHeadscale provides a HTTP REST API and a gRPC interface which may be used to integrate a web interface, remote control Headscale or provide a base for custom integration and tooling.
Both interfaces require a valid API key before use. To create an API key, log into your Headscale server and generate one with the default expiration of 90 days:
headscale apikeys create\nCopy the output of the command and save it for later. Please note that you can not retrieve an API key again. If the API key is lost, expire the old one, and create a new one.
To list the API keys currently associated with the server:
headscale apikeys list\nand to expire an API key:
"},{"location":"ref/api/#rest-api","title":"REST API","text":"headscale apikeys expire --prefix <PREFIX>\n- API endpoint:
/api/v1, e.g.https://headscale.example.com/api/v1 - Documentation:
/swagger, e.g.https://headscale.example.com/swagger - Headscale Version:
/version, e.g.https://headscale.example.com/version - Authenticate using HTTP Bearer authentication by sending the API key with the HTTP
Authorization: Bearer <API_KEY>header.
Start by creating an API key and test it with the examples below. Read the API documentation provided by your Headscale server at
Get details for all usersGet details for user 'bob'Register a node/swaggerfor details.curl -H \"Authorization: Bearer <API_KEY>\" \\\n https://headscale.example.com/api/v1/user\ncurl -H \"Authorization: Bearer <API_KEY>\" \\\n https://headscale.example.com/api/v1/user?name=bob\n
"},{"location":"ref/api/#grpc","title":"gRPC","text":"curl -H \"Authorization: Bearer <API_KEY>\" \\\n -d user=<USER> -d key=<REGISTRATION_KEY> \\\n https://headscale.example.com/api/v1/node/register\nThe gRPC interface can be used to control a Headscale instance from a remote machine with the
"},{"location":"ref/api/#prerequisite","title":"Prerequisite","text":"headscalebinary.- 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.
-
Download the
headscalebinary from GitHub's release page. Make sure to use the same version as on the server. -
Put the binary somewhere in your
PATH, e.g./usr/local/bin/headscale -
Make
headscaleexecutable:chmod +x /usr/local/bin/headscale -
Create an API key on the Headscale server.
-
Provide the connection parameters for the remote Headscale server either via a minimal YAML configuration file or via environment variables:
Minimal YAML configuration fileEnvironment variables config.yamlcli:\n address: <HEADSCALE_ADDRESS>:<PORT>\n api_key: <API_KEY>\nexport HEADSCALE_CLI_ADDRESS=\"<HEADSCALE_ADDRESS>:<PORT>\"\nexport HEADSCALE_CLI_API_KEY=\"<API_KEY>\"\nThis instructs the
headscalebinary to connect to a remote instance at<HEADSCALE_ADDRESS>:<PORT>, instead of connecting to the local instance. -
Test the connection by listing all nodes:
headscale nodes list\nYou 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's 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/api/#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: truein the configuration file or by settingHEADSCALE_CLI_INSECURE=1via an environment variable. We do not recommend to disable certificate validation.
- Headscale loads its configuration from a YAML file
- It searches for
config.yamlin the following paths:/etc/headscale$HOME/.headscale- the current working directory
- To load the configuration from a different path, use:
- the command line flag
-c,--config - the environment variable
HEADSCALE_CONFIG
- the command line flag
- Validate the configuration file with:
headscale configtest
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 withmainbranch might contain unreleased changes.wgetDownload withcurl- Development version: https://github.com/juanfont/headscale/blob/main/config-example.yaml
- Version 0.28.0: https://github.com/juanfont/headscale/blob/v0.28.0/config-example.yaml
# Development version\nwget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml\n\n# Version 0.28.0\nwget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.28.0/config-example.yaml\n
"},{"location":"ref/debug/","title":"Debugging and troubleshooting","text":"# Development version\ncurl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml\n\n# Version 0.28.0\ncurl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.28.0/config-example.yaml\nHeadscale and Tailscale provide debug and introspection capabilities that can be helpful when things don't work as expected. This page explains some debugging techniques to help pinpoint problems.
Please also have a look at Tailscale's Troubleshooting guide. It offers a many tips and suggestions to troubleshoot common issues.
"},{"location":"ref/debug/#tailscale","title":"Tailscale","text":"The Tailscale client itself offers many commands to introspect its state as well as the state of the network:
- Check local network conditions:
tailscale netcheck - Get the client status:
tailscale status --json - Get DNS status:
tailscale dns status --all - Client logs:
tailscale debug daemon-logs - Client netmap:
tailscale debug netmap - Test DERP connection:
tailscale debug derp headscale - And many more, see:
tailscale debug --help
Many of the commands are helpful when trying to understand differences between Headscale and Tailscale SaaS.
"},{"location":"ref/debug/#headscale","title":"Headscale","text":""},{"location":"ref/debug/#application-logging","title":"Application logging","text":"The log levels
debugandtracecan be useful to get more information from Headscale.
"},{"location":"ref/debug/#database-logging","title":"Database logging","text":"log:\n # Valid log levels: panic, fatal, error, warn, info, debug, trace\n level: debug\nThe database debug mode logs all database queries. Enable it to see how Headscale interacts with its database. This also requires the application log level to be set to either
debugortrace.
"},{"location":"ref/debug/#metrics-and-debug-endpoint","title":"Metrics and debug endpoint","text":"database:\n # Enable debug mode. This setting requires the log.level to be set to \"debug\" or \"trace\".\n debug: false\n\nlog:\n # Valid log levels: panic, fatal, error, warn, info, debug, trace\n level: debug\nHeadscale provides a metrics and debug endpoint. It allows to introspect different aspects such as:
- Information about the Go runtime, memory usage and statistics
- Connected nodes and pending registrations
- Active ACLs, filters and SSH policy
- Current DERPMap
- Prometheus metrics
Keep the metrics and debug endpoint private
The listen address and port can be configured with the
metrics_listen_addrvariable in the configuration file. By default it listens on localhost, port 9090.Keep the metrics and debug endpoint private to your internal network and don't expose it to the Internet.
The metrics and debug interface can be disabled completely by setting
metrics_listen_addr: nullin the configuration file.Query metrics via http://localhost:9090/metrics and get an overview of available debug information via http://localhost:9090/debug/. Metrics may be queried from outside localhost but the debug interface is subject to additional protection despite listening on all interfaces.
Direct accessSSH port forwardingVia debug keyVia debug IP addressAccess the debug interface directly on the server where Headscale is installed.
curl http://localhost:9090/debug/\nUse SSH port forwarding to forward Headscale's metrics and debug port to your device.
ssh <HEADSCALE_SERVER> -L 9090:localhost:9090\nAccess the debug interface on your device by opening http://localhost:9090/debug/ in your web browser.
The access control of the debug interface supports the use of a debug key. Traffic is accepted if the path to a debug key is set via the environment variable
TS_DEBUG_KEY_PATHand the debug key sent as value fordebugkeyparameter with each request.openssl rand -hex 32 | tee debugkey.txt\nexport TS_DEBUG_KEY_PATH=debugkey.txt\nheadscale serve\nAccess the debug interface on your device by opening
http://<IP_OF_HEADSCALE>:9090/debug/?debugkey=<DEBUG_KEY>in your web browser. Thedebugkeyparameter must be sent with every request.The debug endpoint expects traffic from localhost. A different debug IP address may be configured by setting the
TS_ALLOW_DEBUG_IPenvironment variable before starting Headscale. The debug IP address is ignored when the HTTP headerX-Forwarded-Foris present.export TS_ALLOW_DEBUG_IP=192.168.0.10 # IP address of your device\nheadscale serve\nAccess the debug interface on your device by opening
"},{"location":"ref/derp/","title":"DERP","text":"http://<IP_OF_HEADSCALE>:9090/debug/in your web browser.A DERP (Designated Encrypted Relay for Packets) server is mainly used to relay traffic between two nodes in case a direct connection can't be established. Headscale provides an embedded DERP server to ensure seamless connectivity between nodes.
"},{"location":"ref/derp/#configuration","title":"Configuration","text":"DERP related settings are configured within the
"},{"location":"ref/derp/#enable-embedded-derp","title":"Enable embedded DERP","text":"derpsection of the configuration file. The following sections only use a few of the available settings, check the example configuration for all available configuration options.Headscale ships with an embedded DERP server which allows to run your own self-hosted DERP server easily. The embedded DERP server is disabled by default and needs to be enabled. In addition, you should configure the public IPv4 and public IPv6 address of your Headscale server for improved connection stability:
config.yamlderp:\n server:\n enabled: true\n ipv4: 198.51.100.1\n ipv6: 2001:db8::1\nKeep in mind that additional ports are needed to run a DERP server. Besides relaying traffic, it also uses STUN (udp/3478) to help clients discover their public IP addresses and perform NAT traversal. Check DERP server connectivity to see if everything works.
"},{"location":"ref/derp/#remove-tailscales-derp-servers","title":"Remove Tailscale's DERP servers","text":"Once enabled, Headscale's embedded DERP is added to the list of free-to-use DERP servers offered by Tailscale Inc. To only use Headscale's embedded DERP server, disable the loading of the default DERP map:
config.yamlderp:\n server:\n enabled: true\n ipv4: 198.51.100.1\n ipv6: 2001:db8::1\n urls: []\nSingle point of failure
Removing Tailscale's DERP servers means that there is now just a single DERP server available for clients. This is a single point of failure and could hamper connectivity.
Check DERP server connectivity with your embedded DERP server before removing Tailscale's DERP servers.
"},{"location":"ref/derp/#customize-derp-map","title":"Customize DERP map","text":"The DERP map offered to clients can be customized with a dedicated YAML-configuration file. This allows to modify previously loaded DERP maps fetched via URL or to offer your own, custom DERP servers to nodes.
Remove specific DERP regionsProvide custom DERP serversThe free-to-use DERP servers are organized into regions via a region ID. You can explicitly disable a specific region by setting its region ID to
derp.yamlnull. The following samplederp.yamldisables the New York DERP region (which has the region ID 1):regions:\n 1: null\nUse the following configuration to serve the default DERP map (excluding New York) to nodes:
config.yamlderp:\n server:\n enabled: false\n urls:\n - https://controlplane.tailscale.com/derpmap/default\n paths:\n - /etc/headscale/derp.yaml\nThe following sample
derp.yamlderp.yamlreferences two custom regions (custom-eastwith ID 900 andcustom-westwith ID 901) with one custom DERP server in each region. Each DERP server offers DERP relay via HTTPS on tcp/443, support for captive portal checks via HTTP on tcp/80 and STUN on udp/3478. See the definitions of DERPMap, DERPRegion and DERPNode for all available options.regions:\n 900:\n regionid: 900\n regioncode: custom-east\n regionname: My region (east)\n nodes:\n - name: 900a\n regionid: 900\n hostname: derp900a.example.com\n ipv4: 198.51.100.1\n ipv6: 2001:db8::1\n canport80: true\n 901:\n regionid: 901\n regioncode: custom-west\n regionname: My Region (west)\n nodes:\n - name: 901a\n regionid: 901\n hostname: derp901a.example.com\n ipv4: 198.51.100.2\n ipv6: 2001:db8::2\n canport80: true\nUse the following configuration to only serve the two DERP servers from the above
config.yamlderp.yaml:derp:\n server:\n enabled: false\n urls: []\n paths:\n - /etc/headscale/derp.yaml\nIndependent of the custom DERP map, you may choose to enable the embedded DERP server and have it automatically added to the custom DERP map.
"},{"location":"ref/derp/#verify-clients","title":"Verify clients","text":"Access to DERP serves can be restricted to nodes that are members of your Tailnet. Relay access is denied for unknown clients.
Embedded DERP3rd-party DERPClient verification is enabled by default.
config.yamlderp:\n server:\n verify_clients: true\nTailscale's
derperprovides two parameters to configure client verification:- Use the
-verify-client-urlparameter of thederperand point it towards the/verifyendpoint of your Headscale server (e.ghttps://headscale.example.com/verify). The DERP server will query your Headscale instance as soon as a client connects with it to ask whether access should be allowed or denied. Access is allowed if Headscale knows about the connecting client and denied otherwise. - The parameter
-verify-client-url-fail-opencontrols what should happen when the DERP server can't reach the Headscale instance. By default, it will allow access if Headscale is unreachable.
Any Tailscale client may be used to introspect the DERP map and to check for connectivity issues with DERP servers.
- Display DERP map:
tailscale debug derp-map - Check connectivity with the embedded DERP1:
tailscale debug derp headscale
Additional DERP related metrics and information is available via the metrics and debug endpoint.
"},{"location":"ref/derp/#limitations","title":"Limitations","text":"- The embedded DERP server can't be used for Tailscale's captive portal checks as it doesn't support the
/generate_204endpoint via HTTP on port tcp/80. - There are no speed or throughput optimisations, the main purpose is to assist in node connectivity.
-
This assumes that the default region code of the configuration file is used.\u00a0\u21a9
Headscale supports most DNS features from Tailscale. DNS related settings can be configured within the
"},{"location":"ref/dns/#setting-extra-dns-records","title":"Setting extra DNS records","text":"dnssection 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_recordsoption in the configuration file for entries that are static and don't change while Headscale is running. Those entries are processed when Headscale is starting up and changes to the configuration require a restart of Headscale. - For dynamic DNS records that may be added, updated or removed while Headscale is running or DNS records that are generated by scripts the option
dns.extra_records_pathin the configuration file is useful. Set it to the absolute path of the JSON file containing DNS records and Headscale processes this file as it detects changes.
An example use case is to serve multiple apps on the same host via a reverse proxy like NGINX, in this case a Prometheus monitoring stack. This allows to nicely access the service with \"http://grafana.myvpn.example.com\" instead of the hostname and port combination \"http://hostname-in-magic-dns.myvpn.example.com:3000\".
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_recordsDynamic entries, viadns.extra_records_pathconfig.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 ...\nRestart 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]\nHeadscale picks up changes to the above JSON file automatically.
Good to know
- The
dns.extra_records_pathoption 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\ndrill -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
Headscale supports authentication via external identity providers using OpenID Connect (OIDC). It features:
- Auto configuration via OpenID Connect Discovery Protocol
- Proof Key for Code Exchange (PKCE) code verification
- Authorization based on a user's domain, email address or group membership
- Synchronization of standard OIDC claims
Please see limitations for known issues and limitations.
"},{"location":"ref/oidc/#configuration","title":"Configuration","text":"OpenID requires configuration in Headscale and your identity provider:
- Headscale: The
oidcsection of the Headscale configuration contains all available configuration options along with a description and their default values. - Identity provider: Please refer to the official documentation of your identity provider for specific instructions. Additionally, there might be some useful hints in the Identity provider specific configuration section below.
A basic configuration connects Headscale to an identity provider and typically requires:
- OpenID Connect Issuer URL from the identity provider. Headscale uses the OpenID Connect Discovery Protocol 1.0 to automatically obtain OpenID configuration parameters (example:
https://sso.example.com). - Client ID from the identity provider (example:
headscale). - Client secret generated by the identity provider (example:
generated-secret). - Redirect URI for your identity provider (example:
https://headscale.example.com/oidc/callback).
oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n- Create a new confidential client (
Client ID,Client secret) - Add Headscale's OIDC callback URL as valid redirect URL:
https://headscale.example.com/oidc/callback - Configure additional parameters to improve user experience such as: name, description, logo, \u2026
Proof Key for Code Exchange (PKCE) adds an additional layer of security to the OAuth 2.0 authorization code flow by preventing authorization code interception attacks, see: https://datatracker.ietf.org/doc/html/rfc7636. PKCE is recommended and needs to be configured for Headscale and the identity provider alike:
HeadscaleIdentity provideroidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n pkce:\n enabled: true\n- Enable PKCE for the headscale client
- Set the PKCE challenge method to \"S256\"
Headscale allows to filter for allowed users based on their domain, email address or group membership. These filters can be helpful to apply additional restrictions and control which users are allowed to join. Filters are disabled by default, users are allowed to join once the authentication with the identity provider succeeds. In case multiple filters are configured, a user needs to pass all of them.
Allowed domainsAllowed users/emailsAllowed groups- Check the email domain of each authenticating user against the list of allowed domains and only authorize users whose email domain matches
example.com. - A verified email address is required unless email verification is disabled.
- Access allowed:
alice@example.com - Access denied:
bob@example.net
oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n allowed_domains:\n - \"example.com\"\n- Check the email address of each authenticating user against the list of allowed email addresses and only authorize users whose email is part of the
allowed_userslist. - A verified email address is required unless email verification is disabled.
- Access allowed:
alice@example.com,bob@example.net - Access denied:
mallory@example.net
oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n allowed_users:\n - \"alice@example.com\"\n - \"bob@example.net\"\n- Use the OIDC
groupsclaim of each authenticating user to get their group membership and only authorize users which are members in at least one of the referenced groups. - Access allowed: users in the
headscale_usersgroup - Access denied: users without groups, users with other groups
"},{"location":"ref/oidc/#control-email-verification","title":"Control email verification","text":"oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n scope: [\"openid\", \"profile\", \"email\", \"groups\"]\n allowed_groups:\n - \"headscale_users\"\nHeadscale uses the
emailclaim from the identity provider to synchronize the email address to its user profile. By default, a user's email address is only synchronized when the identity provider reports the email address as verified via theemail_verified: trueclaim.Unverified emails may be allowed in case an identity provider does not send the
email_verifiedclaim or email verification is not required. In that case, a user's email address is always synchronized to the user profile.
"},{"location":"ref/oidc/#customize-node-expiration","title":"Customize node expiration","text":"oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n email_verified_required: false\nThe node expiration is the amount of time a node is authenticated with OpenID Connect until it expires and needs to reauthenticate. The default node expiration is 180 days. This can either be customized or set to the expiration from the Access Token.
Customize node expirationUse expiration from Access Tokenoidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n expiry: 30d # Use 0 to disable node expiration\nPlease keep in mind that the Access Token is typically a short-lived token that expires within a few minutes. You will have to configure token expiration in your identity provider to avoid frequent re-authentication.
oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n use_expiry_from_token: true\nExpire a node and force re-authentication
A node can be expired immediately via:
"},{"location":"ref/oidc/#reference-a-user-in-the-policy","title":"Reference a user in the policy","text":"headscale node expire -i <NODE_ID>\nYou may refer to users in the Headscale policy via:
- Email address
- Username
- Provider identifier (this value is currently only available from the API, database or directly from your identity provider)
A user identifier in the policy must contain a single
@The Headscale policy requires a single
@to reference a user. If the username or provider identifier doesn't already contain a single@, it needs to be appended at the end. For example: the usernamessmithhas to be written asssmith@to be correctly identified as user within the policy.Email address or username might be updated by users
Many identity providers allow users to update their own profile. Depending on the identity provider and its configuration, the values for username or email address might change over time. This might have unexpected consequences for Headscale where a policy might no longer work or a user might obtain more access by hijacking an existing username or email address.
Howto use the provider identifier in the policy
The provider identifier uniquely identifies an OIDC user and a well-behaving identity provider guarantees that this value never changes for a particular user. It is usually an opaque and long string and its value is currently only available from the API, database or directly from your identity provider).
Use the API with the
/api/v1/userendpoint to fetch the provider identifier (providerId). The value (be sure to append an@in case the provider identifier doesn't already contain an@somewhere) can be used directly to reference a user in the policy. To improve readability of the policy, one may use thegroupssection as an alias:
"},{"location":"ref/oidc/#supported-oidc-claims","title":"Supported OIDC claims","text":"{\n \"groups\": {\n \"group:alice\": [\n \"https://soo.example.com/oauth2/openid/59ac9125-c31b-46c5-814e-06242908cf57@\"\n ]\n },\n \"acls\": [\n {\n \"action\": \"accept\",\n \"src\": [\"group:alice\"],\n \"dst\": [\"*:*\"]\n }\n ]\n}\nHeadscale uses the standard OIDC claims to populate and update its local user profile on each login. OIDC claims are read from the ID Token and from the UserInfo endpoint.
Headscale profile OIDC claim Notes / examples email addressemailOnly verified emails are synchronized, unlessemail_verified_required: falseis configured display namenameeg:Sam Smithusernamepreferred_usernameDepends on identity provider, eg:ssmith,ssmith@idp.example.com,\\\\example.com\\ssmithprofile picturepictureURL to a profile picture or avatar provider identifieriss,subA stable and unique identifier for a user, typically a combination ofissandsubOIDC claimsgroupsOnly used to filter for allowed groups"},{"location":"ref/oidc/#limitations","title":"Limitations","text":"- Support for OpenID Connect aims to be generic and vendor independent. It offers only limited support for quirks of specific identity providers.
- OIDC groups cannot be used in ACLs.
- The username provided by the identity provider needs to adhere to this pattern:
- The username must be at least two characters long.
- It must only contain letters, digits, hyphens, dots, underscores, and up to a single
@. - The username must start with a letter.
Please see the GitHub label \"OIDC\" for OIDC related issues.
"},{"location":"ref/oidc/#identity-provider-specific-configuration","title":"Identity provider specific configuration","text":"Third-party software and services
This section of the documentation is specific for third-party software and services. We recommend users read the third-party documentation on how to configure and integrate an OIDC client. Please see the Configuration section for a description of Headscale's OIDC related configuration settings.
Any identity provider with OpenID Connect support should \"just work\" with Headscale. The following identity providers are known to work:
- Authelia
- Authentik
- Kanidm
- Keycloak
Authelia is fully supported by Headscale.
"},{"location":"ref/oidc/#authentik","title":"Authentik","text":"- Authentik is fully supported by Headscale.
- Headscale does not support JSON Web Encryption. Leave the field
Encryption Keyin the providers section unset.
No username due to missing preferred_username
Google OAuth does not send the
preferred_usernameclaim when the scopeprofileis requested. The username in Headscale will be blank/not set.In order to integrate Headscale with Google, you'll need to have a Google Cloud Console account.
Google OAuth has a verification process if you need to have users authenticate who are outside of your domain. If you only need to authenticate users from your domain name (ie
@example.com), you don't need to go through the verification process.However if you don't have a domain, or need to add users outside of your domain, you can manually add emails via Google Console.
"},{"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, add Headscale's OIDC callback URL:https://headscale.example.com/oidc/callback - Click
Saveat the bottom of the form - Take note of the
Client IDandClient secret, you can also download it for reference if you need it. - Configure Headscale following the \"Basic configuration\" steps. The issuer URL for Google OAuth is:
https://accounts.google.com.
- Kanidm is fully supported by Headscale.
- Groups for the allowed groups filter need to be specified with their full SPN, for example:
headscale_users@sso.example.com. - Kanidm sends the full SPN (
alice@sso.example.com) aspreferred_usernameby default. Headscale stores this value as username which might be confusing as the username and email fields now contain values that look like an email address. Kanidm can be configured to send the short username aspreferred_usernameattribute instead:
Once configured, the short username in Headscale will bekanidm system oauth2 prefer-short-username <client name>\naliceand can be referred to asalice@in the policy.
Keycloak is fully supported by Headscale.
"},{"location":"ref/oidc/#additional-configuration-to-use-the-allowed-groups-filter","title":"Additional configuration to use the allowed groups filter","text":"Keycloak has no built-in client scope for the OIDC
groupsclaim. This extra configuration step is only needed if you need to authorize access based on group membership.- Create a new client scope
groupsfor OpenID Connect:- Configure a
Group Membershipmapper with namegroupsand the token claim namegroups. - Add the mapper to at least the UserInfo endpoint.
- Configure a
- Configure the new client scope for your Headscale client:
- Edit the Headscale client.
- Search for the client scope
group. - Add it with assigned type
Default.
- Configure the allowed groups in Headscale. How groups need to be specified depends on Keycloak's
Full group pathoption:Full group pathis enabled: groups contain their full path, e.g./top/group1Full group pathis disabled: only the name of the group is used, e.g.group1
In order to integrate Headscale with Microsoft Entra ID, you'll need to provision an App Registration with the correct scopes and redirect URI.
Configure Headscale following the \"Basic configuration\" steps. The issuer URL for Microsoft Entra ID is:
https://login.microsoftonline.com/<tenant-UUID>/v2.0. The followingextra_paramsmight be useful:domain_hint: example.comto use your own domainprompt: select_accountto force an account picker during login
When using Microsoft Entra ID together with the allowed groups filter, configure the Headscale OIDC scope without the
groupsclaim, for example:oidc:\n scope: [\"openid\", \"profile\", \"email\"]\nGroups for the allowed groups filter need to be specified with their group ID(UUID) instead of the group name.
"},{"location":"ref/registration/","title":"Registration methods","text":"Headscale supports multiple ways to register a node. The preferred registration method depends on the identity of a node and your use case.
"},{"location":"ref/registration/#identity-model","title":"Identity model","text":"Tailscale's identity model distinguishes between personal and tagged nodes:
- A personal node (or user-owned node) is owned by a human and typically refers to end-user devices such as laptops, workstations or mobile phones. End-user devices are managed by a single user.
- A tagged node (or service-based node or non-human node) provides services to the network. Common examples include web- and database servers. Those nodes are typically managed by a team of users. Some additional restrictions apply for tagged nodes, e.g. a tagged node is not allowed to Tailscale SSH into a personal node.
Headscale implements Tailscale's identity model and distinguishes between personal and tagged nodes where a personal node is owned by a Headscale user and a tagged node is owned by a tag. Tagged devices are grouped under the special user
"},{"location":"ref/registration/#registration-methods_1","title":"Registration methods","text":"tagged-devices.There are two main ways to register new nodes, web authentication and registration with a pre authenticated key. Both methods can be used to register personal and tagged nodes.
"},{"location":"ref/registration/#web-authentication","title":"Web authentication","text":"Web authentication is the default method to register a new node. It's interactive, where the client initiates the registration and the Headscale administrator needs to approve the new node before it is allowed to join the network. A node can be approved with:
- Headscale CLI (described in this documentation)
- Headscale API
- Or delegated to an identity provider via OpenID Connect
Web authentication relies on the presence of a Headscale user. Use the
headscale userscommand to create a new user:
Personal devicesTagged devicesheadscale users create <USER>\nRun
tailscale upto login your personal device:tailscale up --login-server <YOUR_HEADSCALE_URL>\nUsually, a browser window with further instructions is opened. This page explains how to complete the registration on your Headscale server and it also prints the registration key required to approve the node:
headscale nodes register --user <USER> --key <REGISTRATION_KEY>\nCongrations, the registration of your personal node is complete and it should be listed as \"online\" in the output of
headscale nodes list. The \"User\" column displays<USER>as the owner of the node.Your Headscale user needs to be authorized to register tagged devices. This authorization is specified in the
The user alice can register nodes tagged with tag:servertagOwnerssection of the ACL. A simple example looks like this:{\n \"tagOwners\": {\n \"tag:server\": [\"alice@\"]\n },\n // more rules\n}\nRun
tailscale upand provide at least one tag to login a tagged device:tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:<TAG>\nUsually, a browser window with further instructions is opened. This page explains how to complete the registration on your Headscale server and it also prints the registration key required to approve the node:
headscale nodes register --user <USER> --key <REGISTRATION_KEY>\nHeadscale checks that
"},{"location":"ref/registration/#pre-authenticated-key","title":"Pre authenticated key","text":"<USER>is allowed to register a node with the specified tag(s) and then transfers ownership of the new node to the special usertagged-devices. The registration of a tagged node is complete and it should be listed as \"online\" in the output ofheadscale nodes list. The \"User\" column displaystagged-devicesas the owner of the node. See the \"Tags\" column for the list of assigned tags.Registration with a pre authenticated key (or auth key) is a non-interactive way to register a new node. The Headscale administrator creates a preauthkey upfront and this preauthkey can then be used to register a node non-interactively. Its best suited for automation.
Personal devicesTagged devicesA personal node is always assigned to a Headscale user. Use the
headscale userscommand to create a new user:headscale users create <USER>\nUse the
headscale user listcommand to learn its<USER_ID>and create a new pre authenticated key for your user:headscale preauthkeys create --user <USER_ID>\nThe above prints a pre authenticated key with the default settings (can be used once and is valid for one hour). Use this auth key to register a node non-interactively:
tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\nCongrations, the registration of your personal node is complete and it should be listed as \"online\" in the output of
headscale nodes list. The \"User\" column displays<USER>as the owner of the node.Create a new pre authenticated key and provide at least one tag:
headscale preauthkeys create --tags tag:<TAG>\nThe above prints a pre authenticated key with the default settings (can be used once and is valid for one hour). Use this auth key to register a node non-interactively. You don't need to provide the
--advertise-tagsparameter as the tags are automatically read from the pre authenticated key:tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\nThe registration of a tagged node is complete and it should be listed as \"online\" in the output of
"},{"location":"ref/routes/","title":"Routes","text":"headscale nodes list. The \"User\" column displaystagged-devicesas the owner of the node. See the \"Tags\" column for the list of assigned tags.Headscale supports route advertising and can be used to manage subnet routers and exit nodes for a tailnet.
- Subnet routers may be used to connect an existing network such as a virtual private cloud or an on-premise network with your tailnet. Use a subnet router to access devices where Tailscale can't be installed or to gradually rollout Tailscale.
- Exit nodes can be used to route all Internet traffic for another Tailscale node. Use it to securely access the Internet on an untrusted Wi-Fi or to access online services that expect traffic from a specific IP address.
The setup of a subnet router requires double opt-in, once from a subnet router and once on the control server to allow its use within the tailnet. Optionally, use
"},{"location":"ref/routes/#setup-a-subnet-router","title":"Setup a subnet router","text":""},{"location":"ref/routes/#configure-a-node-as-subnet-router","title":"Configure a node as subnet router","text":"autoApproversto automatically approve routes from a subnet router.Register a node and advertise the routes it should handle as comma separated list:
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-routes=10.0.0.0/8,192.168.0.0/24\nIf the node is already registered, it can advertise new routes or update previously announced routes with:
$ sudo tailscale set --advertise-routes=10.0.0.0/8,192.168.0.0/24\nFinally, enable IP forwarding to route traffic.
"},{"location":"ref/routes/#enable-the-subnet-router-on-the-control-server","title":"Enable the subnet router on the control server","text":"The routes of a tailnet can be displayed with the
headscale nodes list-routescommand. A subnet router with the hostnamemyrouterannounced the IPv4 networks10.0.0.0/8and192.168.0.0/24. Those need to be approved before they can be used.$ headscale nodes list-routes\nID | Hostname | Approved | Available | Serving (Primary)\n1 | myrouter | | 10.0.0.0/8 |\n | | | 192.168.0.0/24 |\nApprove all desired routes of a subnet router by specifying them as comma separated list:
$ headscale nodes approve-routes --identifier 1 --routes 10.0.0.0/8,192.168.0.0/24\nNode updated\nThe node
myroutercan now route the IPv4 networks10.0.0.0/8and192.168.0.0/24for the tailnet.
"},{"location":"ref/routes/#use-the-subnet-router","title":"Use the subnet router","text":"$ headscale nodes list-routes\nID | Hostname | Approved | Available | Serving (Primary)\n1 | myrouter | 10.0.0.0/8 | 10.0.0.0/8 | 10.0.0.0/8\n | | 192.168.0.0/24 | 192.168.0.0/24 | 192.168.0.0/24\nTo accept routes advertised by a subnet router on a node:
$ sudo tailscale set --accept-routes\nPlease refer to the official Tailscale documentation for how to use a subnet router on different operating systems.
"},{"location":"ref/routes/#restrict-the-use-of-a-subnet-router-with-acl","title":"Restrict the use of a subnet router with ACL","text":"The routes announced by subnet routers are available to the nodes in a tailnet. By default, without an ACL enabled, all nodes can accept and use such routes. Configure an ACL to explicitly manage who can use routes.
The ACL snippet below defines three hosts, a subnet router
Access the routes of a subnet router without the subnet router itselfrouter, a regular nodenodeandservice.example.netas internal service that can be reached via a route on the subnet routerrouter. It allows the nodenodeto accessservice.example.neton port 80 and 443 which is reachable via the subnet router. Access to the subnet router itself is denied.
"},{"location":"ref/routes/#automatically-approve-routes-of-a-subnet-router","title":"Automatically approve routes of a subnet router","text":"{\n \"hosts\": {\n // the router is not referenced but announces 192.168.0.0/24\"\n \"router\": \"100.64.0.1/32\",\n \"node\": \"100.64.0.2/32\",\n \"service.example.net\": \"192.168.0.1/32\"\n },\n \"acls\": [\n {\n \"action\": \"accept\",\n \"src\": [\"node\"],\n \"dst\": [\"service.example.net:80,443\"]\n }\n ]\n}\nThe initial setup of a subnet router usually requires manual approval of their announced routes on the control server before they can be used by a node in a tailnet. Headscale supports the
autoApproverssection of an ACL to automate the approval of routes served with a subnet router.The ACL snippet below defines the tag
Subnet routers tagged with tag:router are automatically approvedtag:routerowned by the useralice. This tag is used forroutesin theautoApproverssection. The IPv4 route192.168.0.0/24is automatically approved once announced by a subnet router that advertises the tagtag:router.{\n \"tagOwners\": {\n \"tag:router\": [\"alice@\"]\n },\n \"autoApprovers\": {\n \"routes\": {\n \"192.168.0.0/24\": [\"tag:router\"]\n }\n },\n \"acls\": [\n // more rules\n ]\n}\nAdvertise the route
192.168.0.0/24from a subnet router that also advertises the tagtag:routerwhen joining the tailnet:$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:router --advertise-routes 192.168.0.0/24\nPlease see the official Tailscale documentation for more information on auto approvers.
"},{"location":"ref/routes/#exit-node","title":"Exit node","text":"The setup of an exit node requires double opt-in, once from an exit node and once on the control server to allow its use within the tailnet. Optionally, use
"},{"location":"ref/routes/#setup-an-exit-node","title":"Setup an exit node","text":""},{"location":"ref/routes/#configure-a-node-as-exit-node","title":"Configure a node as exit node","text":"autoApproversto automatically approve an exit node.Register a node and make it advertise itself as an exit node:
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-exit-node\nIf the node is already registered, it can advertise exit capabilities like this:
$ sudo tailscale set --advertise-exit-node\nFinally, enable IP forwarding to route traffic.
"},{"location":"ref/routes/#enable-the-exit-node-on-the-control-server","title":"Enable the exit node on the control server","text":"The routes of a tailnet can be displayed with the
headscale nodes list-routescommand. An exit node can be recognized by its announced routes:0.0.0.0/0for IPv4 and::/0for IPv6. The exit node with the hostnamemyexitis already available, but needs to be approved:$ headscale nodes list-routes\nID | Hostname | Approved | Available | Serving (Primary)\n1 | myexit | | 0.0.0.0/0 |\n | | | ::/0 |\nFor exit nodes, it is sufficient to approve either the IPv4 or IPv6 route. The other will be approved automatically.
$ headscale nodes approve-routes --identifier 1 --routes 0.0.0.0/0\nNode updated\nThe node
myexitis now approved as exit node for the tailnet:
"},{"location":"ref/routes/#use-the-exit-node","title":"Use the exit node","text":"$ headscale nodes list-routes\nID | Hostname | Approved | Available | Serving (Primary)\n1 | myexit | 0.0.0.0/0 | 0.0.0.0/0 | 0.0.0.0/0\n | | ::/0 | ::/0 | ::/0\nThe exit node can now be used on a node with:
$ sudo tailscale set --exit-node myexit\nPlease refer to the official Tailscale documentation for how to use an exit node on different operating systems.
"},{"location":"ref/routes/#restrict-the-use-of-an-exit-node-with-acl","title":"Restrict the use of an exit node with ACL","text":"An exit node is offered to all nodes in a tailnet. By default, without an ACL enabled, all nodes in a tailnet can select and use an exit node. Configure
Example use of autogroup:internetautogroup:internetin an ACL rule to restrict who can use any of the available exit nodes.
"},{"location":"ref/routes/#restrict-access-to-exit-nodes-per-user-or-group","title":"Restrict access to exit nodes per user or group","text":"{\n \"acls\": [\n {\n \"action\": \"accept\",\n \"src\": [\"...\"],\n \"dst\": [\"autogroup:internet:*\"]\n }\n ]\n}\nA user can use any of the available exit nodes with
Assign each user a dedicated exit nodeautogroup:internet. Alternatively, the ACL snippet below assigns each user a specific exit node while hiding all other exit nodes. The useralicecan only use exit nodeexit1while userbobcan only use exit nodeexit2.{\n \"hosts\": {\n \"exit1\": \"100.64.0.1/32\",\n \"exit2\": \"100.64.0.2/32\"\n },\n \"acls\": [\n {\n \"action\": \"accept\",\n \"src\": [\"alice@\"],\n \"dst\": [\"exit1:*\"]\n },\n {\n \"action\": \"accept\",\n \"src\": [\"bob@\"],\n \"dst\": [\"exit2:*\"]\n }\n ]\n}\nWarning
- The above implementation is Headscale specific and will likely be removed once support for
viais available. - Beware that a user can also connect to any port of the exit node itself.
The initial setup of an exit node usually requires manual approval on the control server before it can be used by a node in a tailnet. Headscale supports the
autoApproverssection of an ACL to automate the approval of a new exit node as soon as it joins the tailnet.The ACL snippet below defines the tag
Exit nodes tagged with tag:exit are automatically approvedtag:exitowned by the useralice. This tag is used forexitNodein theautoApproverssection. A new exit node that advertises the tagtag:exitis automatically approved:{\n \"tagOwners\": {\n \"tag:exit\": [\"alice@\"]\n },\n \"autoApprovers\": {\n \"exitNode\": [\"tag:exit\"]\n },\n \"acls\": [\n // more rules\n ]\n}\nAdvertise a node as exit node and also advertise the tag
tag:exitwhen joining the tailnet:$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:exit --advertise-exit-node\nPlease see the official Tailscale documentation for more information on auto approvers.
"},{"location":"ref/routes/#high-availability","title":"High availability","text":"Headscale has limited support for high availability routing. Multiple subnet routers with overlapping routes or multiple exit nodes can be used to provide high availability for users. If one router node goes offline, another one can serve the same routes to clients. Please see the official Tailscale documentation on high availability for details.
Bug
In certain situations it might take up to 16 minutes for Headscale to detect a node as offline. A failover node might not be selected fast enough, if such a node is used as subnet router or exit node causing service interruptions for clients. See issue 2129 for more information.
"},{"location":"ref/routes/#troubleshooting","title":"Troubleshooting","text":""},{"location":"ref/routes/#enable-ip-forwarding","title":"Enable IP forwarding","text":"A subnet router or exit node is routing traffic on behalf of other nodes and thus requires IP forwarding. Check the official Tailscale documentation for how to enable IP forwarding.
"},{"location":"ref/tags/","title":"Tags","text":"Headscale supports Tailscale tags. Please read Tailscale's tag documentation to learn how tags work and how to use them.
Tags can be applied during node registration:
- using the
--advertise-tagsflag, see web authentication for tagged devices - using a tagged pre authenticated key, see how to create and use it
Administrators can manage tags with:
- Headscale CLI
- Headscale API
Run
headscale nodes listto list the tags for a node.Use the
headscale nodes tagcommand to modify the tags for a node. At least one tag is required and multiple tags can be provided as comma separated list. The following command sets the tagstag:serverandtag:prodon node with ID 1:
"},{"location":"ref/tags/#convert-from-personal-to-tagged-node","title":"Convert from personal to tagged node","text":"headscale nodes tag -i 1 -t tag:server,tag:prod\nUse the
headscale nodes tagcommand to convert a personal (user-owned) node to a tagged node:headscale nodes tag -i <NODE_ID> -t <TAG>\nThe node is now owned by the special user
"},{"location":"ref/tags/#convert-from-tagged-to-personal-node","title":"Convert from tagged to personal node","text":"tagged-devicesand has the specified tags assigned to it.Tagged nodes can return to personal (user-owned) nodes by re-authenticating with:
tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags= --force-reauth\nUsually, a browser window with further instructions is opened. This page explains how to complete the registration on your Headscale server and it also prints the registration key required to approve the node:
headscale nodes register --user <USER> --key <REGISTRATION_KEY>\nAll previously assigned tags get removed and the node is now owned by the user specified in the above command.
"},{"location":"ref/tls/","title":"Running the service via TLS (optional)","text":""},{"location":"ref/tls/#bring-your-own-certificate","title":"Bring your own certificate","text":"Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the
config.yamltls_cert_pathandtls_key_pathconfiguration 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: \"\"\nThe 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_hostnameto the desired certificate hostname. This name must resolve to the IP address(es) headscale is reachable on (i.e., it must correspond to theserver_urlconfiguration 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\nHeadscale 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_listento 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_listenyou 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_addris 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\nAs these log lines are from the autocert library, they are not strictly generated by headscale itself.
acme/autocert: missing server name\nLikely caused by an incoming connection that does not specify a hostname, for example a
curlrequest directly against the IP of the server, or an unexpected hostname.acme/autocert: host \"[foo]\" not configured in HostWhitelist\nSimilarly 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: \"\"\nThe 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}\nIf 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\nYou 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
EnvoyFilterto 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\nThe following Caddyfile is all that is necessary to use Caddy as a reverse proxy for headscale, in combination with the
Caddyfileconfig.yamlspecifications 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}\nCaddy 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=anyis required as a parameter forProxyPassso that WebSockets traffic whoseUpgradeheader 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>\nCommunity contributions
This page contains community contributions. The projects listed here are not maintained by the headscale authors and are written by community members.
This page collects third-party tools, client libraries, and scripts related to headscale.
- headscale-operator - Headscale Kubernetes Operator
- tailscale-manager - Dynamically manage Tailscale route advertisements
- headscalebacktosqlite - Migrate headscale from PostgreSQL back to SQLite
- headscale-pf - Populates user groups based on user groups in Jumpcloud or Authentik
- headscale-client-go - A Go client implementation for the Headscale HTTP API.
- headscale-zabbix - A Zabbix Monitoring Template for the Headscale Service.
- tailscale-exporter - A Prometheus exporter for Headscale that provides network-level metrics using the Headscale API.
Community contributions
This page contains community contributions. The projects listed here are not maintained by the headscale authors and are written by community members.
Headscale doesn't provide a built-in web interface but users may pick one from the available options.
- headscale-ui - A web frontend for the headscale Tailscale-compatible coordination server
- HeadscaleUi - A static headscale admin ui, no backend environment required
- Headplane - An advanced Tailscale inspired frontend for headscale
- headscale-admin - Headscale-Admin is meant to be a simple, modern web interface for headscale
- ouroboros - Ouroboros is designed for users to manage their own devices, rather than for admins
- unraid-headscale-admin - A simple headscale admin UI for Unraid, it offers Local (
docker exec) and API Mode - headscale-console - WebAssembly-based client supporting SSH, VNC and RDP with optional self-service capabilities
- headscale-piying - headscale web ui,support visual ACL configuration
- HeadControl - Minimal Headscale admin dashboard, built with Go and HTMX
- Headscale Manager - Headscale UI for Android
You can ask for support on our Discord server in the \"web-interfaces\" channel.
"},{"location":"setup/requirements/","title":"Requirements","text":"Headscale should just work as long as the following requirements are met:
- A server with a public IP address for headscale. A dual-stack setup with a public IPv4 and a public IPv6 address is recommended.
- Headscale is served via HTTPS on port 4431 and may use additional ports.
- A reasonably modern Linux or BSD based operating system.
- A dedicated local user account to run headscale.
- A little bit of command line knowledge to configure and operate headscale.
The ports in use vary with the intended scenario and enabled features. Some of the listed ports may be changed via the configuration file but we recommend to stick with the default values.
- tcp/80
- Expose publicly: yes
- HTTP, used by Let's Encrypt to verify ownership via the HTTP-01 challenge.
- Only required if the built-in Let's Enrypt client with the HTTP-01 challenge is used. See TLS for details.
- tcp/443
- Expose publicly: yes
- HTTPS, required to make Headscale available to Tailscale clients1
- Required if the embedded DERP server is enabled
- udp/3478
- Expose publicly: yes
- STUN, required if the embedded DERP server is enabled
- tcp/50443
- Expose publicly: yes
- Only required if the gRPC interface is used to remote-control Headscale.
- tcp/9090
- Expose publicly: no
- Metrics and debug endpoint
The headscale documentation and the provided examples are written with a few assumptions in mind:
- Headscale is running as system service via a dedicated local user
headscale. - The configuration is loaded from
/etc/headscale/config.yaml. - SQLite is used as database.
- The data directory for headscale (used for private keys, 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\u21a9
Required update path
Its required to update from one stable version to the next (e.g. 0.26.0 \u2192 0.27.1 \u2192 0.28.0) without skipping minor versions in between. You should always pick the latest available patch release.
Update an existing Headscale installation to a new version:
- Read the announcement on the GitHub releases page for the new version. It lists the changes of the release along with possible breaking changes and version-specific upgrade instructions.
- Stop Headscale
- Create a backup of your installation
- Update Headscale to the new version, preferably by following the same installation method.
- Compare and update the configuration file.
- Start Headscale
Headscale applies database migrations during upgrades and we highly recommend to create a backup of your database before upgrading. A full backup of Headscale depends on your individual setup, but below are some typical setup scenarios.
Standard installationContainerPostgreSQLA installation that follows our official releases setup guide uses the following paths:
- Configuration file:
/etc/headscale/config.yaml - Data directory:
/var/lib/headscale - SQLite as database:
/var/lib/headscale/db.sqlite
TIMESTAMP=$(date +%Y%m%d%H%M%S)\ncp -aR /etc/headscale /etc/headscale.backup-$TIMESTAMP\ncp -aR /var/lib/headscale /var/lib/headscale.backup-$TIMESTAMP\nA installation that follows our container setup guide uses a single source volume directory that contains the configuration file, data directory and the SQLite database.
cp -aR /path/to/headscale /path/to/headscale.backup-$(date +%Y%m%d%H%M%S)\nPlease follow PostgreSQL's Backup and Restore documentation to create a backup of your PostgreSQL database.
"},{"location":"setup/install/community/","title":"Community packages","text":"Several Linux distributions and community members provide packages for headscale. Those packages may be used instead of the official releases provided by the headscale maintainers. Such packages offer improved integration for their targeted operating system and usually:
- setup a dedicated local 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\nThe AUR package
"},{"location":"setup/install/community/#fedora-rhel-centos","title":"Fedora, RHEL, CentOS","text":"headscale-gitcan 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\nGentoo 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.dand provides usage instructions upon installation.
"},{"location":"setup/install/container/","title":"Running headscale in a container","text":"pkg_add headscale\nCommunity 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. A container runtime such as Docker or Podman is required. The container image can be found on Docker Hub and GitHub Container Registry. The container image URLs are:
- Docker Hub:
docker.io/headscale/headscale:<VERSION> - GitHub Container Registry:
ghcr.io/juanfont/headscale:<VERSION>
-
Create a directory on the container host to store headscale's configuration and the SQLite database:
mkdir -p ./headscale/{config,lib}\ncd ./headscale\n -
Download the example configuration for your chosen version and save it as:
$(pwd)/config/config.yaml. Adjust the configuration to suit your local environment. See Configuration for details. -
Start headscale from within the previously created
./headscaledirectory:docker run \\\n --name headscale \\\n --detach \\\n --read-only \\\n --tmpfs /var/run/headscale \\\n --volume \"$(pwd)/config:/etc/headscale:ro\" \\\n --volume \"$(pwd)/lib:/var/lib/headscale\" \\\n --publish 127.0.0.1:8080:8080 \\\n --publish 127.0.0.1:9090:9090 \\\n --health-cmd \"CMD headscale health\" \\\n docker.io/headscale/headscale:<VERSION> \\\n serve\nNote: use
0.0.0.0:8080:8080instead of127.0.0.1:8080:8080if you want to expose the container externally.This command mounts the local directories inside the container, forwards port 8080 and 9090 out of the container so the headscale instance becomes available and then detaches so headscale runs in the background.
A similar configuration for
docker-compose.yamldocker-compose:services:\n headscale:\n image: docker.io/headscale/headscale:<VERSION>\n restart: unless-stopped\n container_name: headscale\n read_only: true\n tmpfs:\n - /var/run/headscale\n ports:\n - \"127.0.0.1:8080:8080\"\n - \"127.0.0.1:9090:9090\"\n volumes:\n # Please set <HEADSCALE_PATH> to the absolute path\n # of the previously created headscale directory.\n - <HEADSCALE_PATH>/config:/etc/headscale:ro\n - <HEADSCALE_PATH>/lib:/var/lib/headscale\n command: serve\n healthcheck:\n test: [\"CMD\", \"headscale\", \"health\"]\n -
Verify headscale is running:
Follow the container logs:
docker logs --follow headscale\nVerify running containers:
docker ps\nVerify headscale is available:
curl http://127.0.0.1:8080/health\n
Continue on the getting started page to register your first machine.
"},{"location":"setup/install/container/#debugging-headscale-running-in-docker","title":"Debugging headscale running in Docker","text":"The Headscale container image is based on a \"distroless\" image that does not contain a shell or any other debug tools. If you need to debug headscale running in the Docker container, you can use the
"},{"location":"setup/install/container/#running-the-debug-docker-container","title":"Running the debug Docker container","text":"-debugvariant, for exampledocker.io/headscale/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":"docker.io/headscale/headscale:x.x.xwithdocker.io/headscale/headscale:x.x.x-debug(x.x.xis 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/headscaleinside the container.Additionally, the debug container includes a minimalist Busybox shell.
To launch a shell in the container, use:
docker run -it docker.io/headscale/headscale:x.x.x-debug sh\nYou can also execute commands directly, such as
ls /ko-appin this example:docker run docker.io/headscale/headscale:x.x.x-debug ls /ko-app\nUsing
"},{"location":"setup/install/official/","title":"Official releases","text":"docker exec -itallows 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 local user to run headscale, provide a default configuration and ship with a systemd service file. Supported distributions are Ubuntu 22.04 or newer, Debian 12 or newer.
-
Download the latest headscale package for your platform (
.debfor Ubuntu and Debian).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
Continue on the getting started page to register your first machine.
"},{"location":"setup/install/official/#using-standalone-binaries-advanced","title":"Using standalone binaries (advanced)","text":"Advanced
This installation method is considered advanced as one needs to take care of the local 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 local user and the service itself is managed by systemd.
-
Download the latest
headscalebinary from GitHub's release page:sudo wget --output-document=/usr/bin/headscale \\\nhttps://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>\n -
Make
headscaleexecutable:sudo chmod +x /usr/bin/headscale\n -
Add a dedicated local 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.serviceand 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 defaultheadscaleunix socket with a path that is writable by theheadscaleuser 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
Continue on the getting started page to register your first machine.
"},{"location":"setup/install/source/","title":"Build from source","text":"Community documentation
This page is not actively maintained by the headscale authors and is written by community members. It is not verified by headscale developers.
It might be outdated and it might miss necessary steps.
Headscale can be built from source using the latest version of Go and Buf (Protobuf generator). See the Contributing section in the GitHub README for more information.
"},{"location":"setup/install/source/#openbsd","title":"OpenBSD","text":""},{"location":"setup/install/source/#install-from-source","title":"Install from source","text":"
"},{"location":"setup/install/source/#install-from-source-via-cross-compile","title":"Install from source via cross compile","text":"# Install prerequisites\npkg_add go git\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/hscontrol/types.Version=$latestTag\" -X github.com/juanfont/headscale/hscontrol/types.GitCommitHash=HASH\" 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\nThis 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 visiting the health endpoint: https://headscale.example.com/health
- The Tailscale client is installed, see Client and operating system support for more information.
The
NativeContainerheadscalecommand 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# 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\nManage headscale from another local user
By default only the user
headscaleorrootwill have the necessary permissions to access the unix socket (/var/run/headscale/headscale.sock) that is used to communicate with the service. In order to be able to communicate with the headscale service you have to make sure the unix socket is accessible by the user that runs the commands. In general you can achieve this by any of the following methods:- using
sudo - run the commands as user
headscale - add your user to the
headscalegroup
To verify you can run the following command using your preferred method:
"},{"location":"usage/getting-started/#manage-headscale-users","title":"Manage headscale users","text":"headscale users list\nIn headscale, a node (also known as machine or device) is typically assigned to a headscale user. Such a headscale user may have many nodes assigned to them and can be managed with the
"},{"location":"usage/getting-started/#create-a-headscale-user","title":"Create a headscale user","text":"NativeContainerheadscale userscommand. Invoke the built-in help for more information:headscale users --help.headscale users create <USER>\n
"},{"location":"usage/getting-started/#list-existing-headscale-users","title":"List existing headscale users","text":"NativeContainerdocker exec -it headscale \\\n headscale users create <USER>\nheadscale users list\n
"},{"location":"usage/getting-started/#register-a-node","title":"Register a node","text":"docker exec -it headscale \\\n headscale users list\nOne has to register a node first to use headscale as coordination server with Tailscale. The following examples work for the Tailscale client on Linux/BSD operating systems. Alternatively, follow the instructions to connect Android, Apple or Windows devices. Read registration methods for an overview of available registration methods.
"},{"location":"usage/getting-started/#web-authentication","title":"Web authentication","text":"On a client machine, run the
tailscale upcommand and provide the FQDN of your headscale instance as argument:tailscale up --login-server <YOUR_HEADSCALE_URL>\nUsually, a browser window with further instructions is opened. This page explains how to complete the registration on your headscale server and it also prints the registration key required to approve the node:
NativeContainerheadscale nodes register --user <USER> --key <REGISTRATION_KEY>\n
"},{"location":"usage/getting-started/#pre-authenticated-key","title":"Pre authenticated key","text":"docker exec -it headscale \\\n headscale nodes register --user <USER> --key <REGISTRATION_KEY>\nIt 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 --helpfor other options):headscale preauthkeys create --user <USER_ID>\ndocker exec -it headscale \\\n headscale preauthkeys create --user <USER_ID>\nThe command returns the preauthkey on success which is used to connect a node to the headscale instance via the
tailscale upcommand:
"},{"location":"usage/connect/android/","title":"Connecting an Android client","text":"tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\nThis 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/#connect-via-web-authentication","title":"Connect via web authentication","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 - The client connects automatically as soon as the node registration is complete on headscale. Until then, nothing is visible in the server logs.
- Open the app and select the settings menu in the upper-right corner
- Tap on
Accounts - In the kebab menu icon (three dots) in the upper-right corner select
Use an alternate server - Enter your server URL (e.g
https://headscale.example.com). If login prompts open, close it and continue - Open the settings menu in the upper-right corner
- Tap on
Accounts - In the kebab menu icon (three dots) in the upper-right corner select
Use an auth key - Enter your preauthkey generated from headscale
- If needed, tap
Log inon the main screen. You should now be connected to your headscale.
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":"/appleon 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 the Tailscale app
- Click the account icon in the top-right corner and select
Log in\u2026. - Tap the top-right options menu button and select
Use custom coordination server. - Enter your instance url (e.g
https://headscale.example.com) - Enter your credentials and log in. Headscale should now be working on your iOS device.
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 configurationand confirm the appearing popup by clicking theAllowbutton - 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":"/windowson 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>\nFollow 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\"\nin your headscale output, turn on
DEBUGlogging and look for:2022-02-11T00:59:29Z DBG Machine registration has expired. Sending a authurl to register machine=redacted\nThis 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\\Tailscaleand try to connect again. - Ensure the Windows node is deleted from headscale (to ensure fresh setup)
- Start Tailscale on the Windows machine and retry the login.
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 narrow scope, a single Tailscale network (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/windowson your headscale for more information) Android Yes (see docs for more information) macOS Yes (see docs and/appleon your headscale for more information) iOS Yes (see docs and/appleon your headscale for more information) tvOS Yes (see docs and/appleon 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 Tailscale network (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 use GitHub Milestones to plan for upcoming Headscale releases. Have a look at our current plan to get an idea when a specific feature is about to be implemented. The release plan is subject to change at any time.
If you're interested in contributing, please post a feature request about it. Please be aware that there are a number of reasons why we might not accept specific contributions:
- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
- Given that we are reverse-engineering Tailscale to satisfy our own curiosity, we might be interested in implementing the feature ourselves.
- You are not sending unit and integration tests with it.
We currently support deploying headscale using our binaries and the DEB packages. Visit our installation guide using official releases for more information.
In addition to that, you may use packages provided by the community or from distributions. Learn more in the installation guide using community packages.
For convenience, we also build container images with headscale. But please be aware that we don't officially support deploying headscale using Docker. On our Discord server we have a \"docker-issues\" channel where you can ask for Docker-specific help to the community.
"},{"location":"about/faq/#what-is-the-recommended-update-path-can-i-skip-multiple-versions-while-updating","title":"What is the recommended update path? Can I skip multiple versions while updating?","text":"Please follow the steps outlined in the upgrade guide to update your existing Headscale installation. Its required to update from one stable version to the next (e.g. 0.26.0 \u2192 0.27.1 \u2192 0.28.0) without skipping minor versions in between. You should always pick the latest available patch release.
Be sure to check the changelog for version specific upgrade instructions and breaking changes.
"},{"location":"about/faq/#scaling-how-many-clients-does-headscale-support","title":"Scaling / How many clients does Headscale support?","text":"It depends. As often stated, Headscale is not enterprise software and our focus is homelabbers and self-hosters. Of course, we do not prevent people from using it in a commercial/professional setting and often get questions about scaling.
Please note that when Headscale is developed, performance is not part of the consideration as the main audience is considered to be users with a modest amount of devices. We focus on correctness and feature parity with Tailscale SaaS over time.
To understand if you might be able to use Headscale for your use case, I will describe two scenarios in an effort to explain what is the central bottleneck of Headscale:
-
An environment with 1000 servers
- they rarely \"move\" (change their endpoints)
- new nodes are added rarely
-
An environment with 80 laptops/phones (end user devices)
- nodes move often, e.g. switching from home to office
Headscale calculates a map of all nodes that need to talk to each other, creating this \"world map\" requires a lot of CPU time. When an event that requires changes to this map happens, the whole \"world\" is recalculated, and a new \"world map\" is created for every node in the network.
This means that under certain conditions, Headscale can likely handle 100s of devices (maybe more), if there is little to no change happening in the network. For example, in Scenario 1, the process of computing the world map is extremely demanding due to the size of the network, but when the map has been created and the nodes are not changing, the Headscale instance will likely return to a very low resource usage until the next time there is an event requiring the new map.
In the case of Scenario 2, the process of computing the world map is less demanding due to the smaller size of the network, however, the type of nodes will likely change frequently, which would lead to a constant resource usage.
Headscale will start to struggle when the two scenarios overlap, e.g. many nodes with frequent changes will cause the resource usage to remain constantly high. In the worst case scenario, the queue of nodes waiting for their map will grow to a point where Headscale never will be able to catch up, and nodes will never learn about the current state of the world.
We expect that the performance will improve over time as we improve the code base, but it is not a focus. In general, we will never make the tradeoff to make things faster on the cost of less maintainable or readable code. We are a small team and have to optimise for maintainability.
"},{"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 use cases
- Development and testing happens primarily on SQLite
- PostgreSQL is still supported, but is considered to be in \"maintenance mode\"
The headscale project itself does not provide a tool to migrate from PostgreSQL to SQLite. Please have a look at the related tools documentation for migration tooling provided by the community.
The choice of database has little to no impact on the performance of the server, see Scaling / How many clients does Headscale support? for understanding how Headscale spends its resources.
"},{"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/faq/#why-do-two-nodes-see-each-other-in-their-status-even-if-an-acl-allows-traffic-only-in-one-direction","title":"Why do two nodes see each other in their status, even if an ACL allows traffic only in one direction?","text":"A frequent use case is to allow traffic only from one node to another, but not the other way around. For example, the workstation of an administrator should be able to connect to all nodes but the nodes themselves shouldn't be able to connect back to the administrator's node. Why do all nodes see the administrator's workstation in the output of
tailscale status?This is essentially how Tailscale works. If traffic is allowed to flow in one direction, then both nodes see each other in their output of
tailscale status. Traffic is still filtered according to the ACL, with the exception oftailscale pingwhich is always allowed in either direction.See also https://tailscale.com/kb/1087/device-visibility.
"},{"location":"about/faq/#my-policy-is-stored-in-the-database-and-headscale-refuses-to-start-due-to-an-invalid-policy-how-can-i-recover","title":"My policy is stored in the database and Headscale refuses to start due to an invalid policy. How can I recover?","text":"Headscale checks if the policy is valid during startup and refuses to start if it detects an error. The error message indicates which part of the policy is invalid. Follow these steps to fix your policy:
- Dump the policy to a file:
headscale policy get --bypass-grpc-and-access-database-directly > policy.json - Edit and fixup
policy.json. Use the commandheadscale policy check --file policy.jsonto validate the policy. - Load the modified policy:
headscale policy set --bypass-grpc-and-access-database-directly --file policy.json - Start Headscale as usual.
Full server configuration required
The above commands to get/set the policy require a complete server configuration file including database settings. A minimal config to control Headscale via remote CLI is not sufficient. You may use
"},{"location":"about/faq/#how-can-i-migrate-back-to-the-recommended-ip-prefixes","title":"How can I migrate back to the recommended IP prefixes?","text":"headscale -c /path/to/config.yamlto specify the path to an alternative configuration file.Tailscale only supports the IP prefixes
100.64.0.0/10andfd7a:115c:a1e0::/48or smaller subnets thereof. The following steps can be used to migrate from unsupported IP prefixes back to the supported and recommended ones.Backup and test in a demo environment required
The commands below update the IP addresses of all nodes in your tailnet and this might have a severe impact in your specific environment. At a minimum:
- Create a backup of your database
- Test the commands below in a representive demo environment. This allows to catch subsequent connectivity errors early and see how the tailnet behaves in your specific environment.
- Stop Headscale
- Restore the default prefixes in the configuration file:
prefixes:\n v4: 100.64.0.0/10\n v6: fd7a:115c:a1e0::/48\n - Update the
nodes.ipv4andnodes.ipv6columns in the database and assign each node a unique IPv4 and IPv6 address. The following SQL statement assigns IP addresses based on the node ID:UPDATE nodes\nSET ipv4=concat('100.64.', id/256, '.', id%256),\n ipv6=concat('fd7a:115c:a1e0::', format('%x', id));\n - Update the policy to reflect the IP address changes (if any)
- Start Headscale
Nodes should reconnect within a few seconds and pickup their newly assigned IP addresses.
"},{"location":"about/faq/#how-can-i-avoid-to-send-logs-to-tailscale-inc","title":"How can I avoid to send logs to Tailscale Inc?","text":"A Tailscale client collects logs about its operation and connection attempts with other clients and sends them to a central log service operated by Tailscale Inc.
Headscale, by default, instructs clients to disable log submission to the central log service. This configuration is applied by a client once it successfully connected with Headscale. See the configuration option
logtail.enabledin the configuration file for details.Alternatively, logging can also be disabled on the client side. This is independent of Headscale and opting out of client logging disables log submission early during client startup. The configuration is operating system specific and is usually achieved by setting the environment variable
"},{"location":"about/features/","title":"Features","text":"TS_NO_LOGS_NO_SUPPORT=trueor by passing the flag--no-logs-no-supporttotailscaled. See https://tailscale.com/kb/1011/log-mesh-traffic#opting-out-of-client-logging for details.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
- Web authentication
- Pre authenticated key
- DNS
- MagicDNS
- Global and restricted nameservers (split DNS)
- search domains
- Extra DNS records (Headscale only)
- Taildrop (File Sharing)
- Tags
- Routes
- Subnet routers
- Exit nodes
- Dual stack (IPv4 and IPv6)
- Ephemeral nodes
- Embedded DERP server
- Access control lists (GitHub label \"policy\")
- ACL management via API
- Some Autogroups, currently:
autogroup:internet,autogroup:nonroot,autogroup:member,autogroup:tagged,autogroup:self - Auto approvers for subnet routers and exit nodes
- Tailscale SSH
- Node registration using Single-Sign-On (OpenID Connect) (GitHub label \"OIDC\")
- Basic registration
- Update user profile from identity provider
- OIDC groups cannot be used in ACLs
- Funnel (#1040)
- Serve (#1234)
- Network flow logs (#1687)
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 and GitHub Container Registry.
An Atom/RSS feed of headscale releases is available here.
See the \"announcements\" channel on our Discord server for news about headscale.
"},{"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/#acl-setup","title":"ACL Setup","text":"To enable and configure ACLs in Headscale, you need to specify the path to your ACL policy file in the
policy.pathkey inconfig.yaml.Your ACL policy file must be formatted using huJSON.
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 (
"},{"location":"ref/acls/#simple-examples","title":"Simple Examples","text":"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.-
Allow All: If you define an ACL file but completely omit the
\"acls\"field from its content, Headscale will default to an \"allow all\" policy. This means all devices connected to your tailnet will be able to communicate freely with each other.{}\n -
Deny All: To prevent all communication within your tailnet, you can include an empty array for the
\"acls\"field in your policy file.{\n \"acls\": []\n}\n
Let's build a more complex 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
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.Here are the ACL's to implement the same permissions as above:
acl.json
"},{"location":"ref/acls/#autogroups","title":"Autogroups","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.\n {\n \"action\": \"accept\",\n \"src\": [\"group:dev\"],\n \"dst\": [\"10.20.0.0/16:443,5432\"]\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 // Allow users to access their own devices using autogroup:self (see below for more details about performance impact)\n {\n \"action\": \"accept\",\n \"src\": [\"autogroup:member\"],\n \"dst\": [\"autogroup:self:*\"]\n }\n ]\n}\nHeadscale supports several autogroups that automatically include users, destinations, or devices with specific properties. Autogroups provide a convenient way to write ACL rules without manually listing individual users or devices.
"},{"location":"ref/acls/#autogroupinternet","title":"autogroup:internet","text":"Allows access to the internet through exit nodes. Can only be used in ACL destinations.
"},{"location":"ref/acls/#autogroupmember","title":"{\n \"action\": \"accept\",\n \"src\": [\"group:users\"],\n \"dst\": [\"autogroup:internet:*\"]\n}\nautogroup:member","text":"Includes all personal (untagged) devices.
"},{"location":"ref/acls/#autogrouptagged","title":"{\n \"action\": \"accept\",\n \"src\": [\"autogroup:member\"],\n \"dst\": [\"tag:prod-app-servers:80,443\"]\n}\nautogroup:tagged","text":"Includes all devices that have at least one tag.
"},{"location":"ref/acls/#autogroupself","title":"{\n \"action\": \"accept\",\n \"src\": [\"autogroup:tagged\"],\n \"dst\": [\"tag:monitoring:9090\"]\n}\nautogroup:self","text":"The current implementation of
autogroup:selfis inefficientIncludes devices where the same user is authenticated on both the source and destination. Does not include tagged devices. Can only be used in ACL destinations.
Using{\n \"action\": \"accept\",\n \"src\": [\"autogroup:member\"],\n \"dst\": [\"autogroup:self:*\"]\n}\nautogroup:selfmay cause performance degradation on the Headscale coordinator server in large deployments, as filter rules must be compiled per-node rather than globally and the current implementation is not very efficient.If you experience performance issues, consider using more specific ACL rules or limiting the use of
autogroup:self.
"},{"location":"ref/acls/#autogroupnonroot","title":"{\n // The following rules allow internal users to communicate with their\n // own nodes in case autogroup:self is causing performance issues.\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}\nautogroup:nonroot","text":"Used in Tailscale SSH rules to allow access to any user except root. Can only be used in the
usersfield of SSH rules.
"},{"location":"ref/api/","title":"API","text":"{\n \"action\": \"accept\",\n \"src\": [\"autogroup:member\"],\n \"dst\": [\"autogroup:self\"],\n \"users\": [\"autogroup:nonroot\"]\n}\nHeadscale provides a HTTP REST API and a gRPC interface which may be used to integrate a web interface, remote control Headscale or provide a base for custom integration and tooling.
Both interfaces require a valid API key before use. To create an API key, log into your Headscale server and generate one with the default expiration of 90 days:
headscale apikeys create\nCopy the output of the command and save it for later. Please note that you can not retrieve an API key again. If the API key is lost, expire the old one, and create a new one.
To list the API keys currently associated with the server:
headscale apikeys list\nand to expire an API key:
"},{"location":"ref/api/#rest-api","title":"REST API","text":"headscale apikeys expire --prefix <PREFIX>\n- API endpoint:
/api/v1, e.g.https://headscale.example.com/api/v1 - Documentation:
/swagger, e.g.https://headscale.example.com/swagger - Headscale Version:
/version, e.g.https://headscale.example.com/version - Authenticate using HTTP Bearer authentication by sending the API key with the HTTP
Authorization: Bearer <API_KEY>header.
Start by creating an API key and test it with the examples below. Read the API documentation provided by your Headscale server at
Get details for all usersGet details for user 'bob'Register a node/swaggerfor details.curl -H \"Authorization: Bearer <API_KEY>\" \\\n https://headscale.example.com/api/v1/user\ncurl -H \"Authorization: Bearer <API_KEY>\" \\\n https://headscale.example.com/api/v1/user?name=bob\n
"},{"location":"ref/api/#grpc","title":"gRPC","text":"curl -H \"Authorization: Bearer <API_KEY>\" \\\n -d user=<USER> -d key=<REGISTRATION_KEY> \\\n https://headscale.example.com/api/v1/node/register\nThe gRPC interface can be used to control a Headscale instance from a remote machine with the
"},{"location":"ref/api/#prerequisite","title":"Prerequisite","text":"headscalebinary.- 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.
-
Download the
headscalebinary from GitHub's release page. Make sure to use the same version as on the server. -
Put the binary somewhere in your
PATH, e.g./usr/local/bin/headscale -
Make
headscaleexecutable:chmod +x /usr/local/bin/headscale -
Create an API key on the Headscale server.
-
Provide the connection parameters for the remote Headscale server either via a minimal YAML configuration file or via environment variables:
Minimal YAML configuration fileEnvironment variables config.yamlcli:\n address: <HEADSCALE_ADDRESS>:<PORT>\n api_key: <API_KEY>\nexport HEADSCALE_CLI_ADDRESS=\"<HEADSCALE_ADDRESS>:<PORT>\"\nexport HEADSCALE_CLI_API_KEY=\"<API_KEY>\"\nThis instructs the
headscalebinary to connect to a remote instance at<HEADSCALE_ADDRESS>:<PORT>, instead of connecting to the local instance. -
Test the connection by listing all nodes:
headscale nodes list\nYou 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's 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/api/#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: truein the configuration file or by settingHEADSCALE_CLI_INSECURE=1via an environment variable. We do not recommend to disable certificate validation.
- Headscale loads its configuration from a YAML file
- It searches for
config.yamlin the following paths:/etc/headscale$HOME/.headscale- the current working directory
- To load the configuration from a different path, use:
- the command line flag
-c,--config - the environment variable
HEADSCALE_CONFIG
- the command line flag
- Validate the configuration file with:
headscale configtest
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 withmainbranch might contain unreleased changes.wgetDownload withcurl- Development version: https://github.com/juanfont/headscale/blob/main/config-example.yaml
- Version 0.28.0: https://github.com/juanfont/headscale/blob/v0.28.0/config-example.yaml
# Development version\nwget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml\n\n# Version 0.28.0\nwget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.28.0/config-example.yaml\n
"},{"location":"ref/debug/","title":"Debugging and troubleshooting","text":"# Development version\ncurl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml\n\n# Version 0.28.0\ncurl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.28.0/config-example.yaml\nHeadscale and Tailscale provide debug and introspection capabilities that can be helpful when things don't work as expected. This page explains some debugging techniques to help pinpoint problems.
Please also have a look at Tailscale's Troubleshooting guide. It offers a many tips and suggestions to troubleshoot common issues.
"},{"location":"ref/debug/#tailscale","title":"Tailscale","text":"The Tailscale client itself offers many commands to introspect its state as well as the state of the network:
- Check local network conditions:
tailscale netcheck - Get the client status:
tailscale status --json - Get DNS status:
tailscale dns status --all - Client logs:
tailscale debug daemon-logs - Client netmap:
tailscale debug netmap - Test DERP connection:
tailscale debug derp headscale - And many more, see:
tailscale debug --help
Many of the commands are helpful when trying to understand differences between Headscale and Tailscale SaaS.
"},{"location":"ref/debug/#headscale","title":"Headscale","text":""},{"location":"ref/debug/#application-logging","title":"Application logging","text":"The log levels
debugandtracecan be useful to get more information from Headscale.
"},{"location":"ref/debug/#database-logging","title":"Database logging","text":"log:\n # Valid log levels: panic, fatal, error, warn, info, debug, trace\n level: debug\nThe database debug mode logs all database queries. Enable it to see how Headscale interacts with its database. This also requires the application log level to be set to either
debugortrace.
"},{"location":"ref/debug/#metrics-and-debug-endpoint","title":"Metrics and debug endpoint","text":"database:\n # Enable debug mode. This setting requires the log.level to be set to \"debug\" or \"trace\".\n debug: false\n\nlog:\n # Valid log levels: panic, fatal, error, warn, info, debug, trace\n level: debug\nHeadscale provides a metrics and debug endpoint. It allows to introspect different aspects such as:
- Information about the Go runtime, memory usage and statistics
- Connected nodes and pending registrations
- Active ACLs, filters and SSH policy
- Current DERPMap
- Prometheus metrics
Keep the metrics and debug endpoint private
The listen address and port can be configured with the
metrics_listen_addrvariable in the configuration file. By default it listens on localhost, port 9090.Keep the metrics and debug endpoint private to your internal network and don't expose it to the Internet.
The metrics and debug interface can be disabled completely by setting
metrics_listen_addr: nullin the configuration file.Query metrics via http://localhost:9090/metrics and get an overview of available debug information via http://localhost:9090/debug/. Metrics may be queried from outside localhost but the debug interface is subject to additional protection despite listening on all interfaces.
Direct accessSSH port forwardingVia debug keyVia debug IP addressAccess the debug interface directly on the server where Headscale is installed.
curl http://localhost:9090/debug/\nUse SSH port forwarding to forward Headscale's metrics and debug port to your device.
ssh <HEADSCALE_SERVER> -L 9090:localhost:9090\nAccess the debug interface on your device by opening http://localhost:9090/debug/ in your web browser.
The access control of the debug interface supports the use of a debug key. Traffic is accepted if the path to a debug key is set via the environment variable
TS_DEBUG_KEY_PATHand the debug key sent as value fordebugkeyparameter with each request.openssl rand -hex 32 | tee debugkey.txt\nexport TS_DEBUG_KEY_PATH=debugkey.txt\nheadscale serve\nAccess the debug interface on your device by opening
http://<IP_OF_HEADSCALE>:9090/debug/?debugkey=<DEBUG_KEY>in your web browser. Thedebugkeyparameter must be sent with every request.The debug endpoint expects traffic from localhost. A different debug IP address may be configured by setting the
TS_ALLOW_DEBUG_IPenvironment variable before starting Headscale. The debug IP address is ignored when the HTTP headerX-Forwarded-Foris present.export TS_ALLOW_DEBUG_IP=192.168.0.10 # IP address of your device\nheadscale serve\nAccess the debug interface on your device by opening
"},{"location":"ref/derp/","title":"DERP","text":"http://<IP_OF_HEADSCALE>:9090/debug/in your web browser.A DERP (Designated Encrypted Relay for Packets) server is mainly used to relay traffic between two nodes in case a direct connection can't be established. Headscale provides an embedded DERP server to ensure seamless connectivity between nodes.
"},{"location":"ref/derp/#configuration","title":"Configuration","text":"DERP related settings are configured within the
"},{"location":"ref/derp/#enable-embedded-derp","title":"Enable embedded DERP","text":"derpsection of the configuration file. The following sections only use a few of the available settings, check the example configuration for all available configuration options.Headscale ships with an embedded DERP server which allows to run your own self-hosted DERP server easily. The embedded DERP server is disabled by default and needs to be enabled. In addition, you should configure the public IPv4 and public IPv6 address of your Headscale server for improved connection stability:
config.yamlderp:\n server:\n enabled: true\n ipv4: 198.51.100.1\n ipv6: 2001:db8::1\nKeep in mind that additional ports are needed to run a DERP server. Besides relaying traffic, it also uses STUN (udp/3478) to help clients discover their public IP addresses and perform NAT traversal. Check DERP server connectivity to see if everything works.
"},{"location":"ref/derp/#remove-tailscales-derp-servers","title":"Remove Tailscale's DERP servers","text":"Once enabled, Headscale's embedded DERP is added to the list of free-to-use DERP servers offered by Tailscale Inc. To only use Headscale's embedded DERP server, disable the loading of the default DERP map:
config.yamlderp:\n server:\n enabled: true\n ipv4: 198.51.100.1\n ipv6: 2001:db8::1\n urls: []\nSingle point of failure
Removing Tailscale's DERP servers means that there is now just a single DERP server available for clients. This is a single point of failure and could hamper connectivity.
Check DERP server connectivity with your embedded DERP server before removing Tailscale's DERP servers.
"},{"location":"ref/derp/#customize-derp-map","title":"Customize DERP map","text":"The DERP map offered to clients can be customized with a dedicated YAML-configuration file. This allows to modify previously loaded DERP maps fetched via URL or to offer your own, custom DERP servers to nodes.
Remove specific DERP regionsProvide custom DERP serversThe free-to-use DERP servers are organized into regions via a region ID. You can explicitly disable a specific region by setting its region ID to
derp.yamlnull. The following samplederp.yamldisables the New York DERP region (which has the region ID 1):regions:\n 1: null\nUse the following configuration to serve the default DERP map (excluding New York) to nodes:
config.yamlderp:\n server:\n enabled: false\n urls:\n - https://controlplane.tailscale.com/derpmap/default\n paths:\n - /etc/headscale/derp.yaml\nThe following sample
derp.yamlderp.yamlreferences two custom regions (custom-eastwith ID 900 andcustom-westwith ID 901) with one custom DERP server in each region. Each DERP server offers DERP relay via HTTPS on tcp/443, support for captive portal checks via HTTP on tcp/80 and STUN on udp/3478. See the definitions of DERPMap, DERPRegion and DERPNode for all available options.regions:\n 900:\n regionid: 900\n regioncode: custom-east\n regionname: My region (east)\n nodes:\n - name: 900a\n regionid: 900\n hostname: derp900a.example.com\n ipv4: 198.51.100.1\n ipv6: 2001:db8::1\n canport80: true\n 901:\n regionid: 901\n regioncode: custom-west\n regionname: My Region (west)\n nodes:\n - name: 901a\n regionid: 901\n hostname: derp901a.example.com\n ipv4: 198.51.100.2\n ipv6: 2001:db8::2\n canport80: true\nUse the following configuration to only serve the two DERP servers from the above
config.yamlderp.yaml:derp:\n server:\n enabled: false\n urls: []\n paths:\n - /etc/headscale/derp.yaml\nIndependent of the custom DERP map, you may choose to enable the embedded DERP server and have it automatically added to the custom DERP map.
"},{"location":"ref/derp/#verify-clients","title":"Verify clients","text":"Access to DERP serves can be restricted to nodes that are members of your Tailnet. Relay access is denied for unknown clients.
Embedded DERP3rd-party DERPClient verification is enabled by default.
config.yamlderp:\n server:\n verify_clients: true\nTailscale's
derperprovides two parameters to configure client verification:- Use the
-verify-client-urlparameter of thederperand point it towards the/verifyendpoint of your Headscale server (e.ghttps://headscale.example.com/verify). The DERP server will query your Headscale instance as soon as a client connects with it to ask whether access should be allowed or denied. Access is allowed if Headscale knows about the connecting client and denied otherwise. - The parameter
-verify-client-url-fail-opencontrols what should happen when the DERP server can't reach the Headscale instance. By default, it will allow access if Headscale is unreachable.
Any Tailscale client may be used to introspect the DERP map and to check for connectivity issues with DERP servers.
- Display DERP map:
tailscale debug derp-map - Check connectivity with the embedded DERP1:
tailscale debug derp headscale
Additional DERP related metrics and information is available via the metrics and debug endpoint.
"},{"location":"ref/derp/#limitations","title":"Limitations","text":"- The embedded DERP server can't be used for Tailscale's captive portal checks as it doesn't support the
/generate_204endpoint via HTTP on port tcp/80. - There are no speed or throughput optimisations, the main purpose is to assist in node connectivity.
-
This assumes that the default region code of the configuration file is used.\u00a0\u21a9
Headscale supports most DNS features from Tailscale. DNS related settings can be configured within the
"},{"location":"ref/dns/#setting-extra-dns-records","title":"Setting extra DNS records","text":"dnssection 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_recordsoption in the configuration file for entries that are static and don't change while Headscale is running. Those entries are processed when Headscale is starting up and changes to the configuration require a restart of Headscale. - For dynamic DNS records that may be added, updated or removed while Headscale is running or DNS records that are generated by scripts the option
dns.extra_records_pathin the configuration file is useful. Set it to the absolute path of the JSON file containing DNS records and Headscale processes this file as it detects changes.
An example use case is to serve multiple apps on the same host via a reverse proxy like NGINX, in this case a Prometheus monitoring stack. This allows to nicely access the service with \"http://grafana.myvpn.example.com\" instead of the hostname and port combination \"http://hostname-in-magic-dns.myvpn.example.com:3000\".
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_recordsDynamic entries, viadns.extra_records_pathconfig.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 ...\nRestart 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]\nHeadscale picks up changes to the above JSON file automatically.
Good to know
- The
dns.extra_records_pathoption 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\ndrill -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
Headscale supports authentication via external identity providers using OpenID Connect (OIDC). It features:
- Auto configuration via OpenID Connect Discovery Protocol
- Proof Key for Code Exchange (PKCE) code verification
- Authorization based on a user's domain, email address or group membership
- Synchronization of standard OIDC claims
Please see limitations for known issues and limitations.
"},{"location":"ref/oidc/#configuration","title":"Configuration","text":"OpenID requires configuration in Headscale and your identity provider:
- Headscale: The
oidcsection of the Headscale configuration contains all available configuration options along with a description and their default values. - Identity provider: Please refer to the official documentation of your identity provider for specific instructions. Additionally, there might be some useful hints in the Identity provider specific configuration section below.
A basic configuration connects Headscale to an identity provider and typically requires:
- OpenID Connect Issuer URL from the identity provider. Headscale uses the OpenID Connect Discovery Protocol 1.0 to automatically obtain OpenID configuration parameters (example:
https://sso.example.com). - Client ID from the identity provider (example:
headscale). - Client secret generated by the identity provider (example:
generated-secret). - Redirect URI for your identity provider (example:
https://headscale.example.com/oidc/callback).
oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n- Create a new confidential client (
Client ID,Client secret) - Add Headscale's OIDC callback URL as valid redirect URL:
https://headscale.example.com/oidc/callback - Configure additional parameters to improve user experience such as: name, description, logo, \u2026
Proof Key for Code Exchange (PKCE) adds an additional layer of security to the OAuth 2.0 authorization code flow by preventing authorization code interception attacks, see: https://datatracker.ietf.org/doc/html/rfc7636. PKCE is recommended and needs to be configured for Headscale and the identity provider alike:
HeadscaleIdentity provideroidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n pkce:\n enabled: true\n- Enable PKCE for the headscale client
- Set the PKCE challenge method to \"S256\"
Headscale allows to filter for allowed users based on their domain, email address or group membership. These filters can be helpful to apply additional restrictions and control which users are allowed to join. Filters are disabled by default, users are allowed to join once the authentication with the identity provider succeeds. In case multiple filters are configured, a user needs to pass all of them.
Allowed domainsAllowed users/emailsAllowed groups- Check the email domain of each authenticating user against the list of allowed domains and only authorize users whose email domain matches
example.com. - A verified email address is required unless email verification is disabled.
- Access allowed:
alice@example.com - Access denied:
bob@example.net
oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n allowed_domains:\n - \"example.com\"\n- Check the email address of each authenticating user against the list of allowed email addresses and only authorize users whose email is part of the
allowed_userslist. - A verified email address is required unless email verification is disabled.
- Access allowed:
alice@example.com,bob@example.net - Access denied:
mallory@example.net
oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n allowed_users:\n - \"alice@example.com\"\n - \"bob@example.net\"\n- Use the OIDC
groupsclaim of each authenticating user to get their group membership and only authorize users which are members in at least one of the referenced groups. - Access allowed: users in the
headscale_usersgroup - Access denied: users without groups, users with other groups
"},{"location":"ref/oidc/#control-email-verification","title":"Control email verification","text":"oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n scope: [\"openid\", \"profile\", \"email\", \"groups\"]\n allowed_groups:\n - \"headscale_users\"\nHeadscale uses the
emailclaim from the identity provider to synchronize the email address to its user profile. By default, a user's email address is only synchronized when the identity provider reports the email address as verified via theemail_verified: trueclaim.Unverified emails may be allowed in case an identity provider does not send the
email_verifiedclaim or email verification is not required. In that case, a user's email address is always synchronized to the user profile.
"},{"location":"ref/oidc/#customize-node-expiration","title":"Customize node expiration","text":"oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n email_verified_required: false\nThe node expiration is the amount of time a node is authenticated with OpenID Connect until it expires and needs to reauthenticate. The default node expiration is 180 days. This can either be customized or set to the expiration from the Access Token.
Customize node expirationUse expiration from Access Tokenoidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n expiry: 30d # Use 0 to disable node expiration\nPlease keep in mind that the Access Token is typically a short-lived token that expires within a few minutes. You will have to configure token expiration in your identity provider to avoid frequent re-authentication.
oidc:\n issuer: \"https://sso.example.com\"\n client_id: \"headscale\"\n client_secret: \"generated-secret\"\n use_expiry_from_token: true\nExpire a node and force re-authentication
A node can be expired immediately via:
"},{"location":"ref/oidc/#reference-a-user-in-the-policy","title":"Reference a user in the policy","text":"headscale node expire -i <NODE_ID>\nYou may refer to users in the Headscale policy via:
- Email address
- Username
- Provider identifier (this value is currently only available from the API, database or directly from your identity provider)
A user identifier in the policy must contain a single
@The Headscale policy requires a single
@to reference a user. If the username or provider identifier doesn't already contain a single@, it needs to be appended at the end. For example: the usernamessmithhas to be written asssmith@to be correctly identified as user within the policy.Email address or username might be updated by users
Many identity providers allow users to update their own profile. Depending on the identity provider and its configuration, the values for username or email address might change over time. This might have unexpected consequences for Headscale where a policy might no longer work or a user might obtain more access by hijacking an existing username or email address.
Howto use the provider identifier in the policy
The provider identifier uniquely identifies an OIDC user and a well-behaving identity provider guarantees that this value never changes for a particular user. It is usually an opaque and long string and its value is currently only available from the API, database or directly from your identity provider).
Use the API with the
/api/v1/userendpoint to fetch the provider identifier (providerId). The value (be sure to append an@in case the provider identifier doesn't already contain an@somewhere) can be used directly to reference a user in the policy. To improve readability of the policy, one may use thegroupssection as an alias:
"},{"location":"ref/oidc/#supported-oidc-claims","title":"Supported OIDC claims","text":"{\n \"groups\": {\n \"group:alice\": [\n \"https://soo.example.com/oauth2/openid/59ac9125-c31b-46c5-814e-06242908cf57@\"\n ]\n },\n \"acls\": [\n {\n \"action\": \"accept\",\n \"src\": [\"group:alice\"],\n \"dst\": [\"*:*\"]\n }\n ]\n}\nHeadscale uses the standard OIDC claims to populate and update its local user profile on each login. OIDC claims are read from the ID Token and from the UserInfo endpoint.
Headscale profile OIDC claim Notes / examples email addressemailOnly verified emails are synchronized, unlessemail_verified_required: falseis configured display namenameeg:Sam Smithusernamepreferred_usernameDepends on identity provider, eg:ssmith,ssmith@idp.example.com,\\\\example.com\\ssmithprofile picturepictureURL to a profile picture or avatar provider identifieriss,subA stable and unique identifier for a user, typically a combination ofissandsubOIDC claimsgroupsOnly used to filter for allowed groups"},{"location":"ref/oidc/#limitations","title":"Limitations","text":"- Support for OpenID Connect aims to be generic and vendor independent. It offers only limited support for quirks of specific identity providers.
- OIDC groups cannot be used in ACLs.
- The username provided by the identity provider needs to adhere to this pattern:
- The username must be at least two characters long.
- It must only contain letters, digits, hyphens, dots, underscores, and up to a single
@. - The username must start with a letter.
Please see the GitHub label \"OIDC\" for OIDC related issues.
"},{"location":"ref/oidc/#identity-provider-specific-configuration","title":"Identity provider specific configuration","text":"Third-party software and services
This section of the documentation is specific for third-party software and services. We recommend users read the third-party documentation on how to configure and integrate an OIDC client. Please see the Configuration section for a description of Headscale's OIDC related configuration settings.
Any identity provider with OpenID Connect support should \"just work\" with Headscale. The following identity providers are known to work:
- Authelia
- Authentik
- Kanidm
- Keycloak
Authelia is fully supported by Headscale.
"},{"location":"ref/oidc/#authentik","title":"Authentik","text":"- Authentik is fully supported by Headscale.
- Headscale does not support JSON Web Encryption. Leave the field
Encryption Keyin the providers section unset.
No username due to missing preferred_username
Google OAuth does not send the
preferred_usernameclaim when the scopeprofileis requested. The username in Headscale will be blank/not set.In order to integrate Headscale with Google, you'll need to have a Google Cloud Console account.
Google OAuth has a verification process if you need to have users authenticate who are outside of your domain. If you only need to authenticate users from your domain name (ie
@example.com), you don't need to go through the verification process.However if you don't have a domain, or need to add users outside of your domain, you can manually add emails via Google Console.
"},{"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, add Headscale's OIDC callback URL:https://headscale.example.com/oidc/callback - Click
Saveat the bottom of the form - Take note of the
Client IDandClient secret, you can also download it for reference if you need it. - Configure Headscale following the \"Basic configuration\" steps. The issuer URL for Google OAuth is:
https://accounts.google.com.
- Kanidm is fully supported by Headscale.
- Groups for the allowed groups filter need to be specified with their full SPN, for example:
headscale_users@sso.example.com. - Kanidm sends the full SPN (
alice@sso.example.com) aspreferred_usernameby default. Headscale stores this value as username which might be confusing as the username and email fields now contain values that look like an email address. Kanidm can be configured to send the short username aspreferred_usernameattribute instead:
Once configured, the short username in Headscale will bekanidm system oauth2 prefer-short-username <client name>\naliceand can be referred to asalice@in the policy.
Keycloak is fully supported by Headscale.
"},{"location":"ref/oidc/#additional-configuration-to-use-the-allowed-groups-filter","title":"Additional configuration to use the allowed groups filter","text":"Keycloak has no built-in client scope for the OIDC
groupsclaim. This extra configuration step is only needed if you need to authorize access based on group membership.- Create a new client scope
groupsfor OpenID Connect:- Configure a
Group Membershipmapper with namegroupsand the token claim namegroups. - Add the mapper to at least the UserInfo endpoint.
- Configure a
- Configure the new client scope for your Headscale client:
- Edit the Headscale client.
- Search for the client scope
group. - Add it with assigned type
Default.
- Configure the allowed groups in Headscale. How groups need to be specified depends on Keycloak's
Full group pathoption:Full group pathis enabled: groups contain their full path, e.g./top/group1Full group pathis disabled: only the name of the group is used, e.g.group1
In order to integrate Headscale with Microsoft Entra ID, you'll need to provision an App Registration with the correct scopes and redirect URI.
Configure Headscale following the \"Basic configuration\" steps. The issuer URL for Microsoft Entra ID is:
https://login.microsoftonline.com/<tenant-UUID>/v2.0. The followingextra_paramsmight be useful:domain_hint: example.comto use your own domainprompt: select_accountto force an account picker during login
When using Microsoft Entra ID together with the allowed groups filter, configure the Headscale OIDC scope without the
groupsclaim, for example:oidc:\n scope: [\"openid\", \"profile\", \"email\"]\nGroups for the allowed groups filter need to be specified with their group ID(UUID) instead of the group name.
"},{"location":"ref/registration/","title":"Registration methods","text":"Headscale supports multiple ways to register a node. The preferred registration method depends on the identity of a node and your use case.
"},{"location":"ref/registration/#identity-model","title":"Identity model","text":"Tailscale's identity model distinguishes between personal and tagged nodes:
- A personal node (or user-owned node) is owned by a human and typically refers to end-user devices such as laptops, workstations or mobile phones. End-user devices are managed by a single user.
- A tagged node (or service-based node or non-human node) provides services to the network. Common examples include web- and database servers. Those nodes are typically managed by a team of users. Some additional restrictions apply for tagged nodes, e.g. a tagged node is not allowed to Tailscale SSH into a personal node.
Headscale implements Tailscale's identity model and distinguishes between personal and tagged nodes where a personal node is owned by a Headscale user and a tagged node is owned by a tag. Tagged devices are grouped under the special user
"},{"location":"ref/registration/#registration-methods_1","title":"Registration methods","text":"tagged-devices.There are two main ways to register new nodes, web authentication and registration with a pre authenticated key. Both methods can be used to register personal and tagged nodes.
"},{"location":"ref/registration/#web-authentication","title":"Web authentication","text":"Web authentication is the default method to register a new node. It's interactive, where the client initiates the registration and the Headscale administrator needs to approve the new node before it is allowed to join the network. A node can be approved with:
- Headscale CLI (described in this documentation)
- Headscale API
- Or delegated to an identity provider via OpenID Connect
Web authentication relies on the presence of a Headscale user. Use the
headscale userscommand to create a new user:
Personal devicesTagged devicesheadscale users create <USER>\nRun
tailscale upto login your personal device:tailscale up --login-server <YOUR_HEADSCALE_URL>\nUsually, a browser window with further instructions is opened. This page explains how to complete the registration on your Headscale server and it also prints the registration key required to approve the node:
headscale nodes register --user <USER> --key <REGISTRATION_KEY>\nCongrations, the registration of your personal node is complete and it should be listed as \"online\" in the output of
headscale nodes list. The \"User\" column displays<USER>as the owner of the node.Your Headscale user needs to be authorized to register tagged devices. This authorization is specified in the
The user alice can register nodes tagged with tag:servertagOwnerssection of the ACL. A simple example looks like this:{\n \"tagOwners\": {\n \"tag:server\": [\"alice@\"]\n },\n // more rules\n}\nRun
tailscale upand provide at least one tag to login a tagged device:tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:<TAG>\nUsually, a browser window with further instructions is opened. This page explains how to complete the registration on your Headscale server and it also prints the registration key required to approve the node:
headscale nodes register --user <USER> --key <REGISTRATION_KEY>\nHeadscale checks that
"},{"location":"ref/registration/#pre-authenticated-key","title":"Pre authenticated key","text":"<USER>is allowed to register a node with the specified tag(s) and then transfers ownership of the new node to the special usertagged-devices. The registration of a tagged node is complete and it should be listed as \"online\" in the output ofheadscale nodes list. The \"User\" column displaystagged-devicesas the owner of the node. See the \"Tags\" column for the list of assigned tags.Registration with a pre authenticated key (or auth key) is a non-interactive way to register a new node. The Headscale administrator creates a preauthkey upfront and this preauthkey can then be used to register a node non-interactively. Its best suited for automation.
Personal devicesTagged devicesA personal node is always assigned to a Headscale user. Use the
headscale userscommand to create a new user:headscale users create <USER>\nUse the
headscale user listcommand to learn its<USER_ID>and create a new pre authenticated key for your user:headscale preauthkeys create --user <USER_ID>\nThe above prints a pre authenticated key with the default settings (can be used once and is valid for one hour). Use this auth key to register a node non-interactively:
tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\nCongrations, the registration of your personal node is complete and it should be listed as \"online\" in the output of
headscale nodes list. The \"User\" column displays<USER>as the owner of the node.Create a new pre authenticated key and provide at least one tag:
headscale preauthkeys create --tags tag:<TAG>\nThe above prints a pre authenticated key with the default settings (can be used once and is valid for one hour). Use this auth key to register a node non-interactively. You don't need to provide the
--advertise-tagsparameter as the tags are automatically read from the pre authenticated key:tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\nThe registration of a tagged node is complete and it should be listed as \"online\" in the output of
"},{"location":"ref/routes/","title":"Routes","text":"headscale nodes list. The \"User\" column displaystagged-devicesas the owner of the node. See the \"Tags\" column for the list of assigned tags.Headscale supports route advertising and can be used to manage subnet routers and exit nodes for a tailnet.
- Subnet routers may be used to connect an existing network such as a virtual private cloud or an on-premise network with your tailnet. Use a subnet router to access devices where Tailscale can't be installed or to gradually rollout Tailscale.
- Exit nodes can be used to route all Internet traffic for another Tailscale node. Use it to securely access the Internet on an untrusted Wi-Fi or to access online services that expect traffic from a specific IP address.
The setup of a subnet router requires double opt-in, once from a subnet router and once on the control server to allow its use within the tailnet. Optionally, use
"},{"location":"ref/routes/#setup-a-subnet-router","title":"Setup a subnet router","text":""},{"location":"ref/routes/#configure-a-node-as-subnet-router","title":"Configure a node as subnet router","text":"autoApproversto automatically approve routes from a subnet router.Register a node and advertise the routes it should handle as comma separated list:
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-routes=10.0.0.0/8,192.168.0.0/24\nIf the node is already registered, it can advertise new routes or update previously announced routes with:
$ sudo tailscale set --advertise-routes=10.0.0.0/8,192.168.0.0/24\nFinally, enable IP forwarding to route traffic.
"},{"location":"ref/routes/#enable-the-subnet-router-on-the-control-server","title":"Enable the subnet router on the control server","text":"The routes of a tailnet can be displayed with the
headscale nodes list-routescommand. A subnet router with the hostnamemyrouterannounced the IPv4 networks10.0.0.0/8and192.168.0.0/24. Those need to be approved before they can be used.$ headscale nodes list-routes\nID | Hostname | Approved | Available | Serving (Primary)\n1 | myrouter | | 10.0.0.0/8 |\n | | | 192.168.0.0/24 |\nApprove all desired routes of a subnet router by specifying them as comma separated list:
$ headscale nodes approve-routes --identifier 1 --routes 10.0.0.0/8,192.168.0.0/24\nNode updated\nThe node
myroutercan now route the IPv4 networks10.0.0.0/8and192.168.0.0/24for the tailnet.
"},{"location":"ref/routes/#use-the-subnet-router","title":"Use the subnet router","text":"$ headscale nodes list-routes\nID | Hostname | Approved | Available | Serving (Primary)\n1 | myrouter | 10.0.0.0/8 | 10.0.0.0/8 | 10.0.0.0/8\n | | 192.168.0.0/24 | 192.168.0.0/24 | 192.168.0.0/24\nTo accept routes advertised by a subnet router on a node:
$ sudo tailscale set --accept-routes\nPlease refer to the official Tailscale documentation for how to use a subnet router on different operating systems.
"},{"location":"ref/routes/#restrict-the-use-of-a-subnet-router-with-acl","title":"Restrict the use of a subnet router with ACL","text":"The routes announced by subnet routers are available to the nodes in a tailnet. By default, without an ACL enabled, all nodes can accept and use such routes. Configure an ACL to explicitly manage who can use routes.
The ACL snippet below defines three hosts, a subnet router
Access the routes of a subnet router without the subnet router itselfrouter, a regular nodenodeandservice.example.netas internal service that can be reached via a route on the subnet routerrouter. It allows the nodenodeto accessservice.example.neton port 80 and 443 which is reachable via the subnet router. Access to the subnet router itself is denied.
"},{"location":"ref/routes/#automatically-approve-routes-of-a-subnet-router","title":"Automatically approve routes of a subnet router","text":"{\n \"hosts\": {\n // the router is not referenced but announces 192.168.0.0/24\"\n \"router\": \"100.64.0.1/32\",\n \"node\": \"100.64.0.2/32\",\n \"service.example.net\": \"192.168.0.1/32\"\n },\n \"acls\": [\n {\n \"action\": \"accept\",\n \"src\": [\"node\"],\n \"dst\": [\"service.example.net:80,443\"]\n }\n ]\n}\nThe initial setup of a subnet router usually requires manual approval of their announced routes on the control server before they can be used by a node in a tailnet. Headscale supports the
autoApproverssection of an ACL to automate the approval of routes served with a subnet router.The ACL snippet below defines the tag
Subnet routers tagged with tag:router are automatically approvedtag:routerowned by the useralice. This tag is used forroutesin theautoApproverssection. The IPv4 route192.168.0.0/24is automatically approved once announced by a subnet router that advertises the tagtag:router.{\n \"tagOwners\": {\n \"tag:router\": [\"alice@\"]\n },\n \"autoApprovers\": {\n \"routes\": {\n \"192.168.0.0/24\": [\"tag:router\"]\n }\n },\n \"acls\": [\n // more rules\n ]\n}\nAdvertise the route
192.168.0.0/24from a subnet router that also advertises the tagtag:routerwhen joining the tailnet:$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:router --advertise-routes 192.168.0.0/24\nPlease see the official Tailscale documentation for more information on auto approvers.
"},{"location":"ref/routes/#exit-node","title":"Exit node","text":"The setup of an exit node requires double opt-in, once from an exit node and once on the control server to allow its use within the tailnet. Optionally, use
"},{"location":"ref/routes/#setup-an-exit-node","title":"Setup an exit node","text":""},{"location":"ref/routes/#configure-a-node-as-exit-node","title":"Configure a node as exit node","text":"autoApproversto automatically approve an exit node.Register a node and make it advertise itself as an exit node:
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-exit-node\nIf the node is already registered, it can advertise exit capabilities like this:
$ sudo tailscale set --advertise-exit-node\nFinally, enable IP forwarding to route traffic.
"},{"location":"ref/routes/#enable-the-exit-node-on-the-control-server","title":"Enable the exit node on the control server","text":"The routes of a tailnet can be displayed with the
headscale nodes list-routescommand. An exit node can be recognized by its announced routes:0.0.0.0/0for IPv4 and::/0for IPv6. The exit node with the hostnamemyexitis already available, but needs to be approved:$ headscale nodes list-routes\nID | Hostname | Approved | Available | Serving (Primary)\n1 | myexit | | 0.0.0.0/0 |\n | | | ::/0 |\nFor exit nodes, it is sufficient to approve either the IPv4 or IPv6 route. The other will be approved automatically.
$ headscale nodes approve-routes --identifier 1 --routes 0.0.0.0/0\nNode updated\nThe node
myexitis now approved as exit node for the tailnet:
"},{"location":"ref/routes/#use-the-exit-node","title":"Use the exit node","text":"$ headscale nodes list-routes\nID | Hostname | Approved | Available | Serving (Primary)\n1 | myexit | 0.0.0.0/0 | 0.0.0.0/0 | 0.0.0.0/0\n | | ::/0 | ::/0 | ::/0\nThe exit node can now be used on a node with:
$ sudo tailscale set --exit-node myexit\nPlease refer to the official Tailscale documentation for how to use an exit node on different operating systems.
"},{"location":"ref/routes/#restrict-the-use-of-an-exit-node-with-acl","title":"Restrict the use of an exit node with ACL","text":"An exit node is offered to all nodes in a tailnet. By default, without an ACL enabled, all nodes in a tailnet can select and use an exit node. Configure
Example use of autogroup:internetautogroup:internetin an ACL rule to restrict who can use any of the available exit nodes.
"},{"location":"ref/routes/#restrict-access-to-exit-nodes-per-user-or-group","title":"Restrict access to exit nodes per user or group","text":"{\n \"acls\": [\n {\n \"action\": \"accept\",\n \"src\": [\"...\"],\n \"dst\": [\"autogroup:internet:*\"]\n }\n ]\n}\nA user can use any of the available exit nodes with
Assign each user a dedicated exit nodeautogroup:internet. Alternatively, the ACL snippet below assigns each user a specific exit node while hiding all other exit nodes. The useralicecan only use exit nodeexit1while userbobcan only use exit nodeexit2.{\n \"hosts\": {\n \"exit1\": \"100.64.0.1/32\",\n \"exit2\": \"100.64.0.2/32\"\n },\n \"acls\": [\n {\n \"action\": \"accept\",\n \"src\": [\"alice@\"],\n \"dst\": [\"exit1:*\"]\n },\n {\n \"action\": \"accept\",\n \"src\": [\"bob@\"],\n \"dst\": [\"exit2:*\"]\n }\n ]\n}\nWarning
- The above implementation is Headscale specific and will likely be removed once support for
viais available. - Beware that a user can also connect to any port of the exit node itself.
The initial setup of an exit node usually requires manual approval on the control server before it can be used by a node in a tailnet. Headscale supports the
autoApproverssection of an ACL to automate the approval of a new exit node as soon as it joins the tailnet.The ACL snippet below defines the tag
Exit nodes tagged with tag:exit are automatically approvedtag:exitowned by the useralice. This tag is used forexitNodein theautoApproverssection. A new exit node that advertises the tagtag:exitis automatically approved:{\n \"tagOwners\": {\n \"tag:exit\": [\"alice@\"]\n },\n \"autoApprovers\": {\n \"exitNode\": [\"tag:exit\"]\n },\n \"acls\": [\n // more rules\n ]\n}\nAdvertise a node as exit node and also advertise the tag
tag:exitwhen joining the tailnet:$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:exit --advertise-exit-node\nPlease see the official Tailscale documentation for more information on auto approvers.
"},{"location":"ref/routes/#high-availability","title":"High availability","text":"Headscale has limited support for high availability routing. Multiple subnet routers with overlapping routes or multiple exit nodes can be used to provide high availability for users. If one router node goes offline, another one can serve the same routes to clients. Please see the official Tailscale documentation on high availability for details.
Bug
In certain situations it might take up to 16 minutes for Headscale to detect a node as offline. A failover node might not be selected fast enough, if such a node is used as subnet router or exit node causing service interruptions for clients. See issue 2129 for more information.
"},{"location":"ref/routes/#troubleshooting","title":"Troubleshooting","text":""},{"location":"ref/routes/#enable-ip-forwarding","title":"Enable IP forwarding","text":"A subnet router or exit node is routing traffic on behalf of other nodes and thus requires IP forwarding. Check the official Tailscale documentation for how to enable IP forwarding.
"},{"location":"ref/tags/","title":"Tags","text":"Headscale supports Tailscale tags. Please read Tailscale's tag documentation to learn how tags work and how to use them.
Tags can be applied during node registration:
- using the
--advertise-tagsflag, see web authentication for tagged devices - using a tagged pre authenticated key, see how to create and use it
Administrators can manage tags with:
- Headscale CLI
- Headscale API
Run
headscale nodes listto list the tags for a node.Use the
headscale nodes tagcommand to modify the tags for a node. At least one tag is required and multiple tags can be provided as comma separated list. The following command sets the tagstag:serverandtag:prodon node with ID 1:
"},{"location":"ref/tags/#convert-from-personal-to-tagged-node","title":"Convert from personal to tagged node","text":"headscale nodes tag -i 1 -t tag:server,tag:prod\nUse the
headscale nodes tagcommand to convert a personal (user-owned) node to a tagged node:headscale nodes tag -i <NODE_ID> -t <TAG>\nThe node is now owned by the special user
"},{"location":"ref/tags/#convert-from-tagged-to-personal-node","title":"Convert from tagged to personal node","text":"tagged-devicesand has the specified tags assigned to it.Tagged nodes can return to personal (user-owned) nodes by re-authenticating with:
tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags= --force-reauth\nUsually, a browser window with further instructions is opened. This page explains how to complete the registration on your Headscale server and it also prints the registration key required to approve the node:
headscale nodes register --user <USER> --key <REGISTRATION_KEY>\nAll previously assigned tags get removed and the node is now owned by the user specified in the above command.
"},{"location":"ref/tls/","title":"Running the service via TLS (optional)","text":""},{"location":"ref/tls/#bring-your-own-certificate","title":"Bring your own certificate","text":"Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the
config.yamltls_cert_pathandtls_key_pathconfiguration 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: \"\"\nThe 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_hostnameto the desired certificate hostname. This name must resolve to the IP address(es) headscale is reachable on (i.e., it must correspond to theserver_urlconfiguration 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\nHeadscale 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_listento 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_listenyou 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_addris 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\nAs these log lines are from the autocert library, they are not strictly generated by headscale itself.
acme/autocert: missing server name\nLikely caused by an incoming connection that does not specify a hostname, for example a
curlrequest directly against the IP of the server, or an unexpected hostname.acme/autocert: host \"[foo]\" not configured in HostWhitelist\nSimilarly 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: \"\"\nThe 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}\nIf 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\nYou 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
EnvoyFilterto 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\nThe following Caddyfile is all that is necessary to use Caddy as a reverse proxy for headscale, in combination with the
Caddyfileconfig.yamlspecifications 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}\nCaddy 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=anyis required as a parameter forProxyPassso that WebSockets traffic whoseUpgradeheader 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>\nCommunity contributions
This page contains community contributions. The projects listed here are not maintained by the headscale authors and are written by community members.
This page collects third-party tools, client libraries, and scripts related to headscale.
- headscale-operator - Headscale Kubernetes Operator
- tailscale-manager - Dynamically manage Tailscale route advertisements
- headscalebacktosqlite - Migrate headscale from PostgreSQL back to SQLite
- headscale-pf - Populates user groups based on user groups in Jumpcloud or Authentik
- headscale-client-go - A Go client implementation for the Headscale HTTP API.
- headscale-zabbix - A Zabbix Monitoring Template for the Headscale Service.
- tailscale-exporter - A Prometheus exporter for Headscale that provides network-level metrics using the Headscale API.
Community contributions
This page contains community contributions. The projects listed here are not maintained by the headscale authors and are written by community members.
Headscale doesn't provide a built-in web interface but users may pick one from the available options.
- headscale-ui - A web frontend for the headscale Tailscale-compatible coordination server
- HeadscaleUi - A static headscale admin ui, no backend environment required
- Headplane - An advanced Tailscale inspired frontend for headscale
- headscale-admin - Headscale-Admin is meant to be a simple, modern web interface for headscale
- ouroboros - Ouroboros is designed for users to manage their own devices, rather than for admins
- unraid-headscale-admin - A simple headscale admin UI for Unraid, it offers Local (
docker exec) and API Mode - headscale-console - WebAssembly-based client supporting SSH, VNC and RDP with optional self-service capabilities
- headscale-piying - headscale web ui,support visual ACL configuration
- HeadControl - Minimal Headscale admin dashboard, built with Go and HTMX
- Headscale Manager - Headscale UI for Android
You can ask for support on our Discord server in the \"web-interfaces\" channel.
"},{"location":"setup/requirements/","title":"Requirements","text":"Headscale should just work as long as the following requirements are met:
- A server with a public IP address for headscale. A dual-stack setup with a public IPv4 and a public IPv6 address is recommended.
- Headscale is served via HTTPS on port 4431 and may use additional ports.
- A reasonably modern Linux or BSD based operating system.
- A dedicated local user account to run headscale.
- A little bit of command line knowledge to configure and operate headscale.
The ports in use vary with the intended scenario and enabled features. Some of the listed ports may be changed via the configuration file but we recommend to stick with the default values.
- tcp/80
- Expose publicly: yes
- HTTP, used by Let's Encrypt to verify ownership via the HTTP-01 challenge.
- Only required if the built-in Let's Enrypt client with the HTTP-01 challenge is used. See TLS for details.
- tcp/443
- Expose publicly: yes
- HTTPS, required to make Headscale available to Tailscale clients1
- Required if the embedded DERP server is enabled
- udp/3478
- Expose publicly: yes
- STUN, required if the embedded DERP server is enabled
- tcp/50443
- Expose publicly: yes
- Only required if the gRPC interface is used to remote-control Headscale.
- tcp/9090
- Expose publicly: no
- Metrics and debug endpoint
The headscale documentation and the provided examples are written with a few assumptions in mind:
- Headscale is running as system service via a dedicated local user
headscale. - The configuration is loaded from
/etc/headscale/config.yaml. - SQLite is used as database.
- The data directory for headscale (used for private keys, 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\u21a9
Required update path
Its required to update from one stable version to the next (e.g. 0.26.0 \u2192 0.27.1 \u2192 0.28.0) without skipping minor versions in between. You should always pick the latest available patch release.
Update an existing Headscale installation to a new version:
- Read the announcement on the GitHub releases page for the new version. It lists the changes of the release along with possible breaking changes and version-specific upgrade instructions.
- Stop Headscale
- Create a backup of your installation
- Update Headscale to the new version, preferably by following the same installation method.
- Compare and update the configuration file.
- Start Headscale
Headscale applies database migrations during upgrades and we highly recommend to create a backup of your database before upgrading. A full backup of Headscale depends on your individual setup, but below are some typical setup scenarios.
Standard installationContainerPostgreSQLA installation that follows our official releases setup guide uses the following paths:
- Configuration file:
/etc/headscale/config.yaml - Data directory:
/var/lib/headscale - SQLite as database:
/var/lib/headscale/db.sqlite
TIMESTAMP=$(date +%Y%m%d%H%M%S)\ncp -aR /etc/headscale /etc/headscale.backup-$TIMESTAMP\ncp -aR /var/lib/headscale /var/lib/headscale.backup-$TIMESTAMP\nA installation that follows our container setup guide uses a single source volume directory that contains the configuration file, data directory and the SQLite database.
cp -aR /path/to/headscale /path/to/headscale.backup-$(date +%Y%m%d%H%M%S)\nPlease follow PostgreSQL's Backup and Restore documentation to create a backup of your PostgreSQL database.
"},{"location":"setup/install/community/","title":"Community packages","text":"Several Linux distributions and community members provide packages for headscale. Those packages may be used instead of the official releases provided by the headscale maintainers. Such packages offer improved integration for their targeted operating system and usually:
- setup a dedicated local 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\nThe AUR package
"},{"location":"setup/install/community/#fedora-rhel-centos","title":"Fedora, RHEL, CentOS","text":"headscale-gitcan 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\nGentoo 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.dand provides usage instructions upon installation.
"},{"location":"setup/install/container/","title":"Running headscale in a container","text":"pkg_add headscale\nCommunity 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. A container runtime such as Docker or Podman is required. The container image can be found on Docker Hub and GitHub Container Registry. The container image URLs are:
- Docker Hub:
docker.io/headscale/headscale:<VERSION> - GitHub Container Registry:
ghcr.io/juanfont/headscale:<VERSION>
-
Create a directory on the container host to store headscale's configuration and the SQLite database:
mkdir -p ./headscale/{config,lib}\ncd ./headscale\n -
Download the example configuration for your chosen version and save it as:
$(pwd)/config/config.yaml. Adjust the configuration to suit your local environment. See Configuration for details. -
Start headscale from within the previously created
./headscaledirectory:docker run \\\n --name headscale \\\n --detach \\\n --read-only \\\n --tmpfs /var/run/headscale \\\n --volume \"$(pwd)/config:/etc/headscale:ro\" \\\n --volume \"$(pwd)/lib:/var/lib/headscale\" \\\n --publish 127.0.0.1:8080:8080 \\\n --publish 127.0.0.1:9090:9090 \\\n --health-cmd \"CMD headscale health\" \\\n docker.io/headscale/headscale:<VERSION> \\\n serve\nNote: use
0.0.0.0:8080:8080instead of127.0.0.1:8080:8080if you want to expose the container externally.This command mounts the local directories inside the container, forwards port 8080 and 9090 out of the container so the headscale instance becomes available and then detaches so headscale runs in the background.
A similar configuration for
docker-compose.yamldocker-compose:services:\n headscale:\n image: docker.io/headscale/headscale:<VERSION>\n restart: unless-stopped\n container_name: headscale\n read_only: true\n tmpfs:\n - /var/run/headscale\n ports:\n - \"127.0.0.1:8080:8080\"\n - \"127.0.0.1:9090:9090\"\n volumes:\n # Please set <HEADSCALE_PATH> to the absolute path\n # of the previously created headscale directory.\n - <HEADSCALE_PATH>/config:/etc/headscale:ro\n - <HEADSCALE_PATH>/lib:/var/lib/headscale\n command: serve\n healthcheck:\n test: [\"CMD\", \"headscale\", \"health\"]\n -
Verify headscale is running:
Follow the container logs:
docker logs --follow headscale\nVerify running containers:
docker ps\nVerify headscale is available:
curl http://127.0.0.1:8080/health\n
Continue on the getting started page to register your first machine.
"},{"location":"setup/install/container/#debugging-headscale-running-in-docker","title":"Debugging headscale running in Docker","text":"The Headscale container image is based on a \"distroless\" image that does not contain a shell or any other debug tools. If you need to debug headscale running in the Docker container, you can use the
"},{"location":"setup/install/container/#running-the-debug-docker-container","title":"Running the debug Docker container","text":"-debugvariant, for exampledocker.io/headscale/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":"docker.io/headscale/headscale:x.x.xwithdocker.io/headscale/headscale:x.x.x-debug(x.x.xis 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/headscaleinside the container.Additionally, the debug container includes a minimalist Busybox shell.
To launch a shell in the container, use:
docker run -it docker.io/headscale/headscale:x.x.x-debug sh\nYou can also execute commands directly, such as
ls /ko-appin this example:docker run docker.io/headscale/headscale:x.x.x-debug ls /ko-app\nUsing
"},{"location":"setup/install/official/","title":"Official releases","text":"docker exec -itallows 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 local user to run headscale, provide a default configuration and ship with a systemd service file. Supported distributions are Ubuntu 22.04 or newer, Debian 12 or newer.
-
Download the latest headscale package for your platform (
.debfor Ubuntu and Debian).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
Continue on the getting started page to register your first machine.
"},{"location":"setup/install/official/#using-standalone-binaries-advanced","title":"Using standalone binaries (advanced)","text":"Advanced
This installation method is considered advanced as one needs to take care of the local 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 local user and the service itself is managed by systemd.
-
Download the latest
headscalebinary from GitHub's release page:sudo wget --output-document=/usr/bin/headscale \\\nhttps://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>\n -
Make
headscaleexecutable:sudo chmod +x /usr/bin/headscale\n -
Add a dedicated local 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.serviceand 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 defaultheadscaleunix socket with a path that is writable by theheadscaleuser 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
Continue on the getting started page to register your first machine.
"},{"location":"setup/install/source/","title":"Build from source","text":"Community documentation
This page is not actively maintained by the headscale authors and is written by community members. It is not verified by headscale developers.
It might be outdated and it might miss necessary steps.
Headscale can be built from source using the latest version of Go and Buf (Protobuf generator). See the Contributing section in the GitHub README for more information.
"},{"location":"setup/install/source/#openbsd","title":"OpenBSD","text":""},{"location":"setup/install/source/#install-from-source","title":"Install from source","text":"
"},{"location":"setup/install/source/#install-from-source-via-cross-compile","title":"Install from source via cross compile","text":"# Install prerequisites\npkg_add go git\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/hscontrol/types.Version=$latestTag\" -X github.com/juanfont/headscale/hscontrol/types.GitCommitHash=HASH\" 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\nThis 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 visiting the health endpoint: https://headscale.example.com/health
- The Tailscale client is installed, see Client and operating system support for more information.
The
NativeContainerheadscalecommand 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# 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\nManage headscale from another local user
By default only the user
headscaleorrootwill have the necessary permissions to access the unix socket (/var/run/headscale/headscale.sock) that is used to communicate with the service. In order to be able to communicate with the headscale service you have to make sure the unix socket is accessible by the user that runs the commands. In general you can achieve this by any of the following methods:- using
sudo - run the commands as user
headscale - add your user to the
headscalegroup
To verify you can run the following command using your preferred method:
"},{"location":"usage/getting-started/#manage-headscale-users","title":"Manage headscale users","text":"headscale users list\nIn headscale, a node (also known as machine or device) is typically assigned to a headscale user. Such a headscale user may have many nodes assigned to them and can be managed with the
"},{"location":"usage/getting-started/#create-a-headscale-user","title":"Create a headscale user","text":"NativeContainerheadscale userscommand. Invoke the built-in help for more information:headscale users --help.headscale users create <USER>\n
"},{"location":"usage/getting-started/#list-existing-headscale-users","title":"List existing headscale users","text":"NativeContainerdocker exec -it headscale \\\n headscale users create <USER>\nheadscale users list\n
"},{"location":"usage/getting-started/#register-a-node","title":"Register a node","text":"docker exec -it headscale \\\n headscale users list\nOne has to register a node first to use headscale as coordination server with Tailscale. The following examples work for the Tailscale client on Linux/BSD operating systems. Alternatively, follow the instructions to connect Android, Apple or Windows devices. Read registration methods for an overview of available registration methods.
"},{"location":"usage/getting-started/#web-authentication","title":"Web authentication","text":"On a client machine, run the
tailscale upcommand and provide the FQDN of your headscale instance as argument:tailscale up --login-server <YOUR_HEADSCALE_URL>\nUsually, a browser window with further instructions is opened. This page explains how to complete the registration on your headscale server and it also prints the registration key required to approve the node:
NativeContainerheadscale nodes register --user <USER> --key <REGISTRATION_KEY>\n
"},{"location":"usage/getting-started/#pre-authenticated-key","title":"Pre authenticated key","text":"docker exec -it headscale \\\n headscale nodes register --user <USER> --key <REGISTRATION_KEY>\nIt 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 --helpfor other options):headscale preauthkeys create --user <USER_ID>\ndocker exec -it headscale \\\n headscale preauthkeys create --user <USER_ID>\nThe command returns the preauthkey on success which is used to connect a node to the headscale instance via the
tailscale upcommand:
"},{"location":"usage/connect/android/","title":"Connecting an Android client","text":"tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>\nThis 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/#connect-via-web-authentication","title":"Connect via web authentication","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 - The client connects automatically as soon as the node registration is complete on headscale. Until then, nothing is visible in the server logs.
- Open the app and select the settings menu in the upper-right corner
- Tap on
Accounts - In the kebab menu icon (three dots) in the upper-right corner select
Use an alternate server - Enter your server URL (e.g
https://headscale.example.com). If login prompts open, close it and continue - Open the settings menu in the upper-right corner
- Tap on
Accounts - In the kebab menu icon (three dots) in the upper-right corner select
Use an auth key - Enter your preauthkey generated from headscale
- If needed, tap
Log inon the main screen. You should now be connected to your headscale.
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":"/appleon 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 the Tailscale app
- Click the account icon in the top-right corner and select
Log in\u2026. - Tap the top-right options menu button and select
Use custom coordination server. - Enter your instance url (e.g
https://headscale.example.com) - Enter your credentials and log in. Headscale should now be working on your iOS device.
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 configurationand confirm the appearing popup by clicking theAllowbutton - 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":"/windowson 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>\nFollow 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\"\nin your headscale output, turn on
DEBUGlogging and look for:2022-02-11T00:59:29Z DBG Machine registration has expired. Sending a authurl to register machine=redacted\nThis 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\\Tailscaleand try to connect again. - Ensure the Windows node is deleted from headscale (to ensure fresh setup)
- Start Tailscale on the Windows machine and retry the login.
\ No newline at end of file diff --git a/development/sitemap.xml.gz b/development/sitemap.xml.gz index feb5c341..66e95780 100644 Binary files a/development/sitemap.xml.gz and b/development/sitemap.xml.gz differ diff --git a/versions.json b/versions.json index a11e6dbd..d36a8dcd 100644 --- a/versions.json +++ b/versions.json @@ -10,8 +10,8 @@ "version": "0.28.0", "title": "0.28.0", "aliases": [ - "latest", - "stable" + "stable", + "latest" ] }, {https://juanfont.github.io/headscale/development/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/about/clients/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/about/contributing/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/about/faq/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/about/features/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/about/help/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/about/releases/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/about/sponsor/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/acls/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/api/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/configuration/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/debug/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/derp/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/dns/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/oidc/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/registration/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/routes/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/tags/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/tls/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/integration/reverse-proxy/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/integration/tools/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/ref/integration/web-ui/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/setup/requirements/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/setup/upgrade/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/setup/install/community/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/setup/install/container/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/setup/install/official/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/setup/install/source/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/usage/getting-started/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/usage/connect/android/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/usage/connect/apple/ -2026-02-19 +2026-02-20 https://juanfont.github.io/headscale/development/usage/connect/windows/ -2026-02-19 +2026-02-20