Skip to main content
PROJECT(1) PROJECT(1)

NAME

Self-hosted Tailscale with Headscale

Replacing the Tailscale control plane with a self-hosted alternative for complete network sovereignty

SYNOPSIS

Date: December 19, 2025
Status: Complete
Tags: [Networking] [Docker] [VPN] [Self-hosted]
DESCRIPTION

Self-hosted Tailscale with Headscale

Tailscale is great, but trusting a third party with your network topology felt wrong. Enter Headscale - an open-source implementation of the Tailscale control server.

Why Self-host?

  1. Privacy: No third party sees your network structure
  2. Control: Full access to the coordination server
  3. Learning: Understanding WireGuard and DERP servers
  4. Permanence: Not dependent on a company’s business decisions

The Setup

Running on a small VPS with a public IP:

# docker-compose.yml
version: '3.8'
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

Configuration

The config.yaml needs a few key settings:

server_url: https://headscale.yourdomain.com
listen_addr: 0.0.0.0:8080
grpc_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

dns:
  base_domain: tail.net
  nameservers:
    global:
      - 1.1.1.1

Client Registration

Creating a user and pre-auth key:

# Create a user namespace
docker exec headscale headscale users create homelab

# Generate a pre-auth key
docker exec headscale headscale preauthkeys create \
  --user homelab \
  --reusable \
  --expiration 24h

Then on the client:

tailscale up --login-server=https://headscale.yourdomain.com \
  --authkey=YOUR_PREAUTHKEY

DERP Relay

For NAT traversal, I’m running my own DERP server. This ensures traffic never touches third-party relay servers:

regions:
  900:
    regionid: 900
    regioncode: home
    regionname: Home Lab
    nodes:
      - name: home-derp
        regionid: 900
        hostname: derp.yourdomain.com
        ipv4: YOUR_PUBLIC_IP
        stunport: 3478
        derpport: 443

Results

All 15 devices across 3 locations now communicate through my own control plane. The coordination server runs on a $5/month VPS and handles everything perfectly.

No more third-party involvement in my network topology.

manipulate.org
up 731d _