Create a private network with Headscale and Tailscale
This post describes the usage of headscale
and tailscale
to create a virtual network based on WireGuard.
Installation
One headscale
server node is needed, nodes will be registered on this server using tailscale
.
The couple headscale
/tailscale
permits to establish WireGuard tunnels between all the registered nodes.
If a direct connection between two nodes is not possible, headscale
supports “DERP” to relay the traffic between these two nodes.
headscale
also supports “MagicDNS”, and can automatically map nodes to domain names.
Headscale server
Follow the instructions here. In example for Debian 12:
- Install the package
# HEADSCALE_VERSION="0.24.2"
# HEADSCALE_ARCH="amd64"
# wget --output-document=headscale.deb \
# "https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb"
# apt install ./headscale.deb
- Configure
headscale
by editing the configuration file/etc/headscale/config.yaml
.
The important configuration variables are:
server_url
, in my casehttps://flip.flap42.eu:8080
listen_addr
, set to0.0.0.0:8080
tls_letsencrypt_hostname
, this is the domain used for the TLS certificate with let’s encrypt, I’ve usedflip.flap42.eu
prefixes
, these are the prefix used on your network, they must be contained in100.64.0.0/10
andfd7a:115c:a1e0::/48
. In my case, I’ve set the v6 prefix tofd28:7515:7d51:42::/64
, which is officially unsupported but functional anyway.magic_dns
, set to “true” to enable its supportbase_domain
, base domain used for the magic dns recordsnameservers
, list of forwarders
- Enable the service
# systemctl enable --now headscale
- Verify the service status and read the logs
# systemctl status headscale
# journalctl -u headscale.service -f -n 100
Tailscale client
You can choose the appropriated repository for your system here, for Debian 12:
# curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
# curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.tailscale-keyring.list | sudo tee /etc/apt/sources.list.d/tailscale.list
# apt update
# apt install tailscale
# systemctl status tailscaled
The tailscaled
service is enabled by default.
Register the nodes
- Create users on the server node
Create users
# headscale user create nl-ams1
User created
# headscale user create nl-ams1
User created
# headscale user create nl-ams2
User created
- Use the
tailscale
client on the client node to login to the server. It will open a browser with the registration instructions for theheadscale
server.
# tailscale up --login-server https://flip.flap42.eu:8080
- Register the node on the server node, using the previously generated key
# headscale nodes register --user nl-ams2 --key mkey:**KEY**
Node nl-ams2 registered
- On the client node, use these commands to verify the allocated IPs
$ tailscale ip -4
100.64.0.3
$ tailscale ip -6
fd28:7515:7d51:42::3
As an alternative, you can use preauthorized keys:
- Generate preauthorized keys on the server node
# headscale preauthkeys create -u nl-ams1
2025-01-26T22:21:30+01:00 TRC expiration has been set expiration=3600000
***KEY***
- Use the key on the client node to login
# tailscale up --login-server https://flip.flap42.eu:8080 --authkey **KEY**
Test the virtual network
- On the server node, you can list all clients with the command
headscale node list
# headscale node list
ID | Hostname | Name | MachineKey | NodeKey | User | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired
1 | hc-xps15 | hc-xps15 | [*****] | [*****] | hcartiaux | 100.64.0.3, fd28:7515:7d51:42::3 | false | 2025-01-26 21:45:52 | 0001-01-01 00:00:00 | online | no
2 | nl-ams2 | nl-ams2 | [*****] | [*****] | nl-ams2 | 100.64.0.4, fd28:7515:7d51:42::4 | false | 2025-01-26 21:45:52 | 0001-01-01 00:00:00 | online | no
3 | nl-ams1 | nl-ams1 | [*****] | [*****] | nl-ams1 | 100.64.0.5, fd28:7515:7d51:42::5 | false | 2025-01-26 21:45:52 | 0001-01-01 00:00:00 | online | no
- On the client nodes,
tailscale
create a new network interfacetailscale0
and configures the allocated address
$ ip -br -c a
...
tailscale0 UNKNOWN 100.64.0.4/32 fd28:7515:7d51:42::4/128 fe80::5c6e:ccf5:de0:ea97/64
- All client nodes can reach all the other nodes which are part of the network, using the “MagicDNS”
$ ping -c3 nl-ams1.flip.flap42.eu
PING nl-ams1.flip.flap42.eu (100.64.0.5) 56(84) bytes of data.
64 bytes from nl-ams1.flip.flap42.eu (100.64.0.5): icmp_seq=1 ttl=64 time=4.61 ms
64 bytes from nl-ams1.flip.flap42.eu (100.64.0.5): icmp_seq=2 ttl=64 time=1.85 ms
64 bytes from nl-ams1.flip.flap42.eu (100.64.0.5): icmp_seq=3 ttl=64 time=1.77 ms
--- nl-ams1.flip.flap42.eu ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 1.774/2.741/4.605/1.317 ms