Self-Hosting Tailscale: A Guide to Deploying Headscale on Docker

HomeLab tutorial - IT technology blog
HomeLab tutorial - IT technology blog

Six Months Without the Cloud: My Headscale Journey

I spent years building my HomeLab before hitting a wall: CGNAT. While Tailscale solved the connectivity issue instantly, I didn’t love the idea of my network metadata sitting on a corporate server. I also wanted to bypass the 100-node limit of the free tier without jumping to an enterprise plan. That is when I moved my entire mesh to Headscale.

Headscale is an open-source, self-hosted implementation of the Tailscale control server. It provides the same features you love—NAT traversal, WireGuard security, and seamless P2P connectivity—but keeps the “brain” of the operation on your hardware. After half a year of running this across 25 devices in three different locations, it has become the most stable part of my stack.

Quick Start: Up and Running in 5 Minutes

You can get Headscale live on a $5/month VPS or an old local machine in minutes. All you need is a Linux host with a public IP or a properly forwarded port.

1. Prepare the Directory Structure

Start by creating a dedicated space for your configuration and database files.

mkdir -p ~/headscale/config
cd ~/headscale
touch config/config.yaml

2. The Docker Compose File

Create a docker-compose.yml file. I recommend the official image because it is lightweight and receives security patches quickly.

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

3. Basic Configuration

The default config.yaml is quite long, but you only need to verify a few key fields to get started. Make sure your database path matches your Docker volume.

server_url: http://<YOUR_SERVER_IP>:8080
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 0.0.0.0:9090
db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite

derp:
  urls:
    - https://controlplane.tailscale.com/derpmap/default

Run docker-compose up -d. Your private control plane is now live.

How the Architecture Works

Tailscale uses a split-plane architecture. The Data Plane handles your actual traffic, which is end-to-end encrypted and usually travels directly between devices. The Control Plane (Headscale) only manages node registration and key exchanges.

This setup is powerful because it decouples your security from third-party uptime. When a device joins, it checks in with your Docker container to find its peers. If the public internet goes down but your local devices can still reach your Headscale instance, your internal mesh stays fully operational.

Creating Your First User

Headscale uses “users” to group nodes. You must create at least one user before you can connect your first laptop or phone:

docker exec headscale headscale users create mynetwork

Advanced Usage: Bridging the Gaps

Once the basics are set, you can unlock features that make Headscale feel like a professional networking tool.

Subnet Routers: Accessing Legacy Hardware

I have several 1080p IP cameras and an old brother printer that cannot run Tailscale. To fix this, I turned a Raspberry Pi 4 into a Subnet Router. This allows me to access the entire 192.168.1.0/24 range from my phone while I’m at a coffee shop.

On the client device, run:

tailscale up --login-server http://<YOUR_IP>:8080 --advertise-routes=192.168.1.0/24

Then, enable the route on your Headscale server:

docker exec headscale headscale nodes list
docker exec headscale headscale routes enable -r <ID>

Automating with Pre-Auth Keys

Manual logins are frustrating when you’re spinning up temporary Docker containers or CI/CD runners. I use reusable Pre-Auth keys to let new nodes join the mesh automatically without human intervention:

docker exec headscale headscale preauthkeys create -u mynetwork --reusable --expiration 24h

Lessons from 6 Months in Production

Relying on this for half a year has taught me a few things about maintaining a stable mesh.

  • Use a Reverse Proxy: Exposing port 8080 directly is risky. Use Nginx Proxy Manager or Caddy to handle HTTPS. Mobile clients connect much more reliably over port 443 with a valid SSL certificate.
  • Protect Your Database: The db.sqlite file is the heart of your network. If it gets corrupted, you will have to manually re-authenticate every single node. I use a simple cron job to back this up to my NAS every night.
  • Enable MagicDNS: Headscale supports internal DNS. It is much easier to type ssh proxmox.mynetwork.mesh than it is to memorize a dozen 100.64.x.x IP addresses.
  • The iOS Secret: Changing the server on iPhone is tricky. Open the Tailscale app, go to the settings, and tap the “Tailscale” logo at the top about ten times. This reveals the hidden field to enter your Headscale URL.

Switching to Headscale was the best move I made for my HomeLab. It eliminated my anxiety over future pricing changes and gave me total control over my network map. If you want a private, high-performance VPN without the cloud strings attached, this is the way to go.

Share: