Tailscale and Headscale
Clouduler does not talk to Tailscale or Headscale itself. A tailnet is just a network your nodes can use: the Tailscale client creates a tailscale0 interface with a private 100.x.x.x address on each machine, and you tell Clouduler to use those addresses as its internal network. This is how you run one cluster across several sites. This guide covers enrolling a machine into a tailnet, running your own control server (Headscale) as a stack on the cluster, and where to go for the full multi-site walkthrough.
What you'll end up with
- Machines enrolled in a tailnet, either on Tailscale's hosted service or on your own Headscale.
- Optionally Headscale running as a Clouduler stack, reachable at
https://headscale.example.com.
Prerequisites
- Linux machines with systemd and root access.
- For your own Headscale: a Clouduler cluster, a public network with a real domain, and ideally Authentik for sign-in (Headscale example).
Install the Tailscale client
On Debian, Ubuntu and Raspberry Pi OS:
curl -fsSL https://tailscale.com/install.sh | sh
On Arch Linux, install the tailscale package and start the service:
sudo pacman -S tailscale
sudo systemctl enable --now tailscaled
You should now see tailscaled running: systemctl status tailscaled.
Join Tailscale's hosted tailnet
sudo tailscale up
This prints a login URL. Open it in a browser and approve the machine. Check with tailscale status and tailscale ip -4.
Join your own Headscale
Headscale is a self-hosted Tailscale control server. The client is the same; you point it at your server with --login-server.
Option A: approve in the browser
sudo tailscale up --login-server https://headscale.example.com --accept-dns=false
It prints a URL containing an auth ID. On the Headscale side approve the node:
headscale auth register --user <USER> --auth-id <AUTH_ID>
Option B: a pre-auth key (no browser)
On the Headscale side, create a user once and a key:
headscale users create cluster
headscale users list
headscale preauthkeys create --user <USER_ID>
The default key is single-use and valid for one hour. Run headscale preauthkeys create --help for the expiry, reusable and --tags flags of your version, since they have changed between releases. On the machine:
sudo tailscale up --login-server https://headscale.example.com --auth-key=<KEY> --accept-dns=false
You should now see the machine in tailscale status, and tailscale ip -4 prints its address, for example 100.64.0.2.
Tailscale does not persist tailscale up flags between runs. Repeat --login-server and --accept-dns=false on every later tailscale up, or the next one silently goes back to the default control server. If the machine was logged into another control server before, add --force-reauth.
--accept-dns=false on cluster nodesWith MagicDNS and override_local_dns on, Headscale can rewrite the node's resolver, which Clouduler's own DNS and the node rely on. Turning DNS acceptance off on cluster nodes is the safe default.
Run Headscale on the cluster
The Headscale example has the full stack (Headscale and its admin UI Headplane, with sign-in through Authentik). The Clouduler-specific points are:
-
Publish it on a public network so every client can reach
https://headscale.example.comover the internet without the tailnet. That route is what clients log in against. -
Pin it to one stable node, ideally one with a public address, and back up its volume (
db.sqlite,noise_private.key). -
Create the first user and key inside the container:
clouduler exec headscale.headscale.0 -- headscale users create adminclouduler exec headscale.headscale.0 -- headscale preauthkeys create --user <USER_ID> -
Never put the Docker socket of nodes you do not control behind Headplane: a writable Docker socket is root on the node.
Use the tailnet as Clouduler's network
Read each node's tailnet address with tailscale ip -4 and use it as the internal address in cluster.yaml; do not guess it, because Headscale hands addresses out sequentially. All three cluster ports (7373, 7374, 7375 by default) then run over tailscale0.
The internal network has to exist before the cluster forms, but a tailnet hosted by a stack on the cluster does not exist until the cluster does. A running node's internal address can be changed with a config edit (see Changing a node's address), but only in the order the multi-site quick start walks through: the new network must be up on every node first.
See Multi-site with Headscale for the whole path, including what happens after a total outage. Change the tailscale MTU before the first stack deploy: Docker overlay networks pin their MTU when created, and tailscale0 defaults to 1280.
Test it
tailscale statuslists the other machines.ping 100.64.0.2from another enrolled machine answers.- On the Headscale side,
headscale nodes listshows the node. - Behind a NAT, direct connections need UDP 41641 outbound; otherwise Tailscale falls back to relays over outbound 443.
Troubleshooting
| You see | Cause | Fix |
|---|---|---|
tailscale up goes to tailscale.com instead of your server | --login-server was left out on a later run. | Repeat all flags every time. |
| Login URL opens but the node never appears | You forgot to approve it. | Run headscale auth register --user <USER> --auth-id <AUTH_ID>. |
| Pre-auth key rejected | It is single-use and expires after an hour by default. | Create a new one, or check headscale preauthkeys create --help. |
| Cannot reach Headscale after a power cut | Headscale is a stack and needs the cluster to be scheduled. | See the outage section of the multi-site quick start and Emergency mode. |
| Overlay networks break after changing MTU | Docker pins the MTU at creation. | Set the MTU before the first deploy. |