this post is still a work in progress
why?
i recently switched all my machines to nix and nixos. this includes my desktop computer, my laptop, my vps and my phone (nix-on-droid)
i wanted to setup a vpn so that any of them could access any other without any issues, and headscale seems to be popular these days so i gave it a try
how?
prerequisites
1. the first thing you need is a domain, i used headscale.juke.fr, make sure you setup an A record that points to the host that will host the headscale server
you can also use a dynamic dns service but setting one of these up is not covered in this guide but if you go that route you might want to look into ddclient
2. then you need to make sure that 80/TCP, 443/TCP and 3478/UDP are all allowed in your firewall (for nixos this is covered later in this guide, but if you are running on a VPS like i am on oracle you might need to also open them elsewhere)
headscale nixos configuration
create a headscale.nix
module that you import in your nixos configuration that contains the following, adjust accoring to your needs
{
config,
...
}:
let
domain = "juke.fr"; # domain to use
derpPort = 3478; # default derp port
in
{
services = {
# enable headscale service and configure
headscale = {
enable = true;
address = "127.0.0.1";
port = 8085; # use any port you please, it gets reversed tunnelled anyways
settings = {
dns_config = {
override_local_dns = true;
base_domain = domain;
magic_dns = true; # this enables you to access hosts via $HOSTNAME.$USER.juke.fr
domains = [ "hs.${domain}" ];
nameservers = [
"1.1.1.1"
"9.9.9.9"
];
};
server_url = "https://headscale.${domain}";
metrics_listen_addr = "127.0.0.1:8095"; # use any port you please, it gets reversed tunnelled anyways
logtail = {
enabled = false;
};
log = {
level = "warn";
};
derp.server = {
enable = true;
region_id = 999;
stun_listen_addr = "0.0.0.0:${toString derpPort}";
};
};
};
# reverse proxy with ssl
nginx = {
enable = true;
virtualHosts."headscale.${domain}" = {
forceSSL = true;
enableACME = true;
locations = {
"/" = {
proxyPass = "http://localhost:${toString config.services.headscale.port}";
proxyWebsockets = true;
};
"/metrics" = {
proxyPass = "http://${config.services.headscale.settings.metrics_listen_addr}/metrics";
};
};
};
};
};
# configure ssl certificate options
security.acme = {
defaults.email = "acme@juke.fr";
acceptTerms = true;
};
# punch through firewall
networking.firewall.allowedUDPPorts = [ derpPort ];
networking.firewall.allowedTCPPorts = [
80
443
];
# add headscale package to system
environment.systemPackages = [ config.services.headscale.package ];
}
switch nixos configurations and make sure everything is working correctly by accessing headscale.juke.fr/metrics
you also need to create a namespace (which will create a user) for use later on
sudo headscale namespaces create net # replace net with the namespace name you want
tailscale nixos configuration
next we want to setup the tailscale service in tailscale.nix
and import it in our common configuration for everybody to use
{
lib,
...
}:
{
services.tailscale = {
enable = true;
useRoutingFeatures = lib.mkDefault "client";
};
networking.firewall = {
checkReversePath = "loose";
allowedUDPPorts = [ 41641 ]; # Facilitate firewall punching
};
}
rebuild nixos and now to join the headscale instance we can use
sudo tailscale up --login-server https://headscale.juke.fr/
this will direct you to a login page that will generate a command you should type on the headscale server replacing $USER
with the namespace we created earlier, don't forget to add sudo also
your client is now connected to headscale and you can access it with $HOSTNAME.$NAMESPACE.$domain
for example nixos-home.net.juke.fr