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.

Share
Tailscale DIY Control Center
Photo by Scott Rodgerson / Unsplash

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 DERP relay servers (you may self-host them too)
  • Official Tailscale clients:
    • You use the original unchanged ones supplied by Tailscale for:
      iOS, Android, Linux, macOS and Windows
  • 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

  1. DNS and TLS Setup
  2. Docker Compose Configuration
  3. Create a User and Pre-Auth Key
  4. Register Devices
  5. Enable Subnet Routing
  6. 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.yaml

With:

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 -d
Follow the container logs:
docker compose logs -f headscale
Verify availability:
curl http://127.0.0.1:8080/health

Create 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.

ℹ️
Copy the key — you'll need to use it when joining any device.

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:

ℹ️
If you prefer a GUI – Use a community-built Headscale UI like Headscale-ui, Headplane or any other in the list.

List all nodes

docker exec headscale headscale nodes list

Rename a node

docker exec headscale headscale nodes rename --identifier <id> --new-name myserver

Delete 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
⚠️
Check the Headscale releases for breaking changes before upgrading!
⚠️
The 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.

⚠️
Always check the release notes, before updating, for braking changes!

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]


  1. Tailscale homepage, getting started GitHub ↩︎

  2. 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! ↩︎

  3. Headscale-UI GitHub – A web frontend for the headscale Tailscale-compatible coordination server. ↩︎

  4. Headplane GitHub - An advanced Tailscale inspired frontend for headscale) ↩︎

  5. 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 ↩︎