Tailscale DIY Control Center
Running Tailscale is really easy and you can use their Control Center. But, how about total autonomy and self-host the Tailscale Control Center on your own server or a VPS. It is possible, and this post is about setting it all up.
Self-Host a Tailscale Control Server
Tailscale makes WireGuard mesh networking easy. You can use Tailscale's cloud based Control Center. Meaning that, every device in your tailnet will authenticate through their servers. If you care about privacy, data sovereignty, or simply want to avoid vendor dependency – you might prefer to self-host the Control Server.
Headscale
Headscale is an open-source, self-hosted reimplementation of the Tailscale control server. Aims to implement a self-hosted, open source alternative to the Tailscale control server.
The design goal is to provide us self-hosting enthusiasts with an open-source server for our homelab.
Headscale implements a narrow scope, a single Tailnet (Tailscale network), suitable for a personal use, or a small open-source team.
- Runs on your infrastructure
- Your devices uses the official Tailscale clients
- They phone home to your server instead of Tailscale's
- A fully self-contained mesh VPN that you control
About Headscale
- Full WireGuard mesh:
- Every device gets a direct encrypted tunnel to every other device, with NAT traversal via
DERPrelay servers (you may self-host them too)
- Every device gets a direct encrypted tunnel to every other device, with NAT traversal via
- Official Tailscale clients:
- You use the original unchanged ones supplied by Tailscale for:
iOS, Android, Linux, macOS and Windows
- You use the original unchanged ones supplied by Tailscale for:
- No Tailscale account required:
- No SaaS login, no usage limits, no feature tiers
- Subnet routing:
- Expose your entire home or lab LAN or the segments across the tailnet
- Exit nodes:
- Route all traffic through a trusted node. Looks like you work in the lab
What you gain
- You manage the control plane yourself
- Total autonomy
What you need to supply
- Patch Headscale when needed
- Handling TLS certificates lifecycles
- Occasionally debug and fix client issues
Prerequisites
- A Linux server with a public IP (or a reverse proxy in front of it)
- A domain name for the Headscale server (e.g.
headscale.example.com) - Port 443 or 8080 accessible from the internet
- Tight security in place
- Hardening the server (both HW or virtual -servers)
- Docker or a bare-metal install, I prefer to use Docker Compose
Set up
You can set up the server with Docker Compose i six steps
- DNS and TLS Setup
- Docker Compose Configuration
- Create a User and Pre-Auth Key
- Register Devices
- Enable Subnet Routing
- Verify Connectivity
DNS and TLS Setup
Headscale clients connect over HTTPS. Create a DNS A record pointing headscale.example.com to your server's public IP. If your server is behind NAT, set up port forwarding for 443/TCP.
For TLS, Caddy handles certificates automatically if you use it as a reverse proxy:
headscale.yourdomain.com {
reverse_proxy localhost:8080
}
Alternatively, point an nginx proxy manager or Traefik instance at port 8080.
Docker Compose Configuration
On the Headscale site you'll find a non-Docker setup guide
with example files on GitHub.
Create a directory and config file according to your Docker file management strategy:
mkdir -p /opt/headscale/config /opt/headscale/data
cd /opt/headscale
nano config/config.yamlWith:
server_url: https://headscale.example.com
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 0.0.0.0:9090
private_key_path: /var/lib/headscale/private.key
noise:
private_key_path: /var/lib/headscale/noise_private.key
ip_prefixes:
- 100.64.0.0/10
derp:
server:
enabled: false
urls:
- https://controlplane.tailscale.com/derpmap/default
db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite
acme_url: ""
tls_cert_path: ""
tls_key_path: ""
log:
level: info
dns_config:
override_local_dns: true
nameservers:
- 1.1.1.1
- 1.0.0.1
- 2606:4700:4700::1111
- 2606:4700:4700::1001
magic_dns: true
base_domain: example.internal
Using Tailscale's DERP servers. You might need to fill in the ACME info.
IP Range
The ip_prefixes uses Tailscale's CGNAT range. Devices get IPs in 100.64.0.0/10.
Magic DNS
The magic_dns lets you reach devices by hostname instead of IP:
- e.g.
ping myserver.example.internal
The Compose.yml
Create a Docker compose.yml file
services:
headscale:
image: headscale/headscale:latest
container_name: headscale
restart: unless-stopped
read_only: true
security_opt:
- no-new-privileges:true
volumes:
- ./config:/etc/headscale:ro
- ./data:/var/lib/headscale
ports:
- "8080:8080"
- "9090:9090"
command: serve
healthcheck:
test: ["CMD", "headscale", "health"]
Start the container
docker compose up -dFollow the container logs:
docker compose logs -f headscaleVerify availability:
curl http://127.0.0.1:8080/healthCreate a User and a Pre-Auth Key
Headscale organizes devices into users (formerly: namespaces). Create one:
docker exec headscale headscale users create homelab
Generate a pre-auth key, so devices can register without interactive browser login:
docker exec -it headscale \
headscale preauthkeys create --user <USER_ID>docker exec headscale headscale preauthkeys create \
--user homelab \
--reusable \
--expiration 24h
Without the -- reusable flag it's single use only. Without the --expiration flag it's valid for 1h.
Register Devices
Install the official Tailscale client, on any of your device you want to have Tailnet functionality. Then log in pointing it at your Headscale server:
Linux
Use the CLI
sudo tailscale up \
--login-server https://headscale.example.com \
--authkey <your-pre-auth-key>
macOS
Open a terminal and run:
tailscale login \
--login-server https://headscale.example.com \
--authkey <your-pre-auth-key>
iOS/Android
Install the app from the app/play-store.
In the Tailscale app, tap your account → Use custom control server → enter your Headscale URL, then log in.
Verify the device registered:
docker exec headscale headscale nodes list
You should see the device with an assigned IP like 100.100.0.1.
Enable Subnet Routing
To expose your home LAN (e.g., 192.0.2.0/24) to all tailnet devices, run on your homelab server:
sudo tailscale up \
--login-server https://headscale.yourdomain.com \
--advertise-routes=192.0.2.0/24
Then approve the route on the Headscale server:
docker exec headscale headscale routes list
docker exec headscale headscale routes enable -r <route-id>
Other tailnet devices can now reach 192.0.2.x addresses directly through the mesh.
Verify Connectivity
Make sure you have a launher in your PATH (/usr/local/bin/tailscale)
From any registered device:
tailscale status # see all peers and their IPs
tailscale ping <peer-ip> # verify direct tunnel
The tailscale ping output will tell you if the connection is direct (fast) or relayed through a DERP server it's slower, but it's still encrypted.
Most home-to-home connections go direct once NAT traversal completes.
Security
You need to harden any server you choose to run an app like this.
What is a DERP Server
Tailscale's DERP servers act as relay fallbacks when direct WireGuard tunnels can't be established. Headscale uses Tailscale's public DERP servers by default.
A Self-Hosted DERP Relay Server
This is an optional thing to do
To remove this last external dependency, run your own:
# In config.yaml
derp:
server:
enabled: true
region_id: 999
region_code: "homelab"
region_name: "Homelab"
stun_listen_addr: 0.0.0.0:3478
Expose port 3478/UDP and 443/TCP for DERP, and clients will prefer your relay.
Managing the Tailnet
Headscale has CLI for all management tasks:
List all nodes
docker exec headscale headscale nodes listRename a node
docker exec headscale headscale nodes rename --identifier <id> --new-name myserverDelete a node
docker exec headscale headscale nodes delete --identifier <id>Expire a Pre-Auth Key
docker exec headscale headscale preauthkeys expire --user homelab <key>Upgrade your Headscale
Headscale updates frequently. With Docker Compose, upgrading is:
docker compose pull
docker compose up -d
config.yaml schema occasionally changes between minor versions.Tailscale or Headscale?
That is a tricky question to answer. But, for a homelab that is self-hosting everything else, Headscale fits into mix.
When to Use Headscale
Headscale is the right choice when you want zero external dependencies, full control over your mesh, or need more than Tailscale's free tier allows.
Once set up, it's pretty maintenance-free. Your devices stay connected, subnets stay routed. Some odd things may happen with nonskilled users, as usually.
When to Use Tailscale SaaS
The official SaaS is easier to operate and gets new features first like Tailscale SSH and MagicDNS enhancements.
References
Tailscale [1] Headscale [2] Headscale-UI [3] Headplane [4] Other GUIs [5]
Headscale, Headscale is an open source, self-hosted implementation of the Tailscale control server: homepage, getting started Documentation, GitHub, Docker,
Check Releas notes before upgrading! ↩︎Headscale-UI GitHub – A web frontend for the headscale Tailscale-compatible coordination server. ↩︎
Headplane GitHub - An advanced Tailscale inspired frontend for headscale) ↩︎
Other GUIs to consider See list at
HeadscaleUi - A static headscale admin ui, no backend environment required
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]https://github.com/ahmadzip/HeadControl) - Minimal Headscale admin dashboard, built with Go and HTMX
Headscale Manager - Headscale UI for Android
Headscale UI - Headscale UI online and Self-hosting ↩︎