← All articles
a group of colorful objects

Headscale: Self-Host Your Own Tailscale Control Server

Networking 2026-03-07 · 4 min read headscale tailscale wireguard vpn self-hosted
By HomeLab Starter Editorial TeamHome lab enthusiasts covering hardware setup, networking, and self-hosted services for home and small office environments.

Tailscale makes WireGuard mesh networking trivially easy — but it relies on Tailscale's cloud control plane. Every device in your tailnet authenticates through their servers. For homelab operators who care about privacy, data sovereignty, or simply avoiding vendor dependency, that's a meaningful caveat.

Photo by Aakash Dhage on Unsplash

Headscale is an open-source, self-hosted reimplementation of the Tailscale control server. You run it on your own infrastructure. Your devices still use the official Tailscale clients, but they phone home to your server instead of Tailscale's. The result: a fully self-contained mesh VPN you control completely.

What You Get with Headscale

The tradeoff: you manage the control plane yourself. That means patching Headscale, handling TLS, and occasionally debugging client registration quirks.

Prerequisites

Step 1: DNS and TLS Setup

Headscale clients connect over HTTPS. Create a DNS A record pointing headscale.yourdomain.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 or Traefik instance at port 8080.

Like what you're reading? Subscribe to HomeLab Starter — free weekly guides in your inbox.

Step 2: Docker Compose Configuration

Create a directory and config file:

mkdir -p /opt/headscale/config /opt/headscale/data
cd /opt/headscale

Create config/config.yaml:

server_url: https://headscale.yourdomain.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
  magic_dns: true
  base_domain: yourdomain.internal

The ip_prefixes uses Tailscale's CGNAT range — devices get IPs in 100.64.0.0/10. magic_dns lets you reach devices by hostname instead of IP (e.g., ping myserver.yourdomain.internal).

Create docker-compose.yml:

services:
  headscale:
    image: headscale/headscale:latest
    container_name: headscale
    restart: unless-stopped
    volumes:
      - ./config:/etc/headscale
      - ./data:/var/lib/headscale
    ports:
      - "8080:8080"
      - "9090:9090"
    command: serve

Start it:

docker compose up -d
docker compose logs -f headscale

Step 3: Create a User and Pre-Auth Key

Headscale organizes devices into users (previously called 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 headscale headscale preauthkeys create \
  --user homelab \
  --reusable \
  --expiration 24h

Copy the key — you'll use it when joining each device.

Step 4: Register Devices

On each device, install the official Tailscale client. Then log in pointing at your Headscale server:

Linux:

sudo tailscale up \
  --login-server https://headscale.yourdomain.com \
  --authkey <your-pre-auth-key>

macOS/Windows: Open a terminal and run:

tailscale login \
  --login-server https://headscale.yourdomain.com \
  --authkey <your-pre-auth-key>

iOS/Android: 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.64.0.1.

Step 5: Enable Subnet Routing

To expose your home LAN (e.g., 192.168.1.0/24) to all tailnet devices, run on your homelab server:

sudo tailscale up \
  --login-server https://headscale.yourdomain.com \
  --advertise-routes=192.168.1.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.168.1.x addresses directly through the mesh.

Step 6: Verify Connectivity

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 (slower, but still encrypted). Most home-to-home connections go direct once NAT traversal completes.

Optional: Self-Host DERP Relay Servers

Tailscale's DERP servers act as relay fallbacks when direct WireGuard tunnels can't be established. By default, Headscale uses Tailscale's public DERP servers. 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 ships a CLI for all management tasks:

# List all nodes
docker exec headscale headscale nodes list

# Rename a node
docker exec headscale headscale nodes rename --identifier <id> --new-name myserver

# Remove a node
docker exec headscale headscale nodes delete --identifier <id>

# Expire a pre-auth key
docker exec headscale headscale preauthkeys expire --user homelab <key>

There's also a community-built web UI called Headscale-UI if you prefer a browser interface over the CLI.

Upgrade Path

Headscale updates frequently. With Docker Compose, upgrading is:

docker compose pull
docker compose up -d

Check the Headscale releases for breaking config changes before upgrading — the config.yaml schema occasionally changes between minor versions.

When to Use Headscale vs. Tailscale SaaS

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. The official SaaS is easier to operate and gets new features first (like Tailscale SSH and MagicDNS enhancements). For a homelab that's already self-hosting everything else, Headscale fits naturally into the stack.

Once set up, it's largely maintenance-free. Your devices stay connected, subnets stay routed, and no monthly subscription renews.

Get free weekly tips in your inbox. Subscribe to HomeLab Starter