Skip to content

Playbook: Tailscale to Headscale Migration

What it is

This playbook is a step-by-step operational guide for migrating a mesh network from the Tailscale SaaS coordination server to Headscale, an open-source, self-hosted implementation of the Tailscale control server.

What problem it solves

It eliminates dependency on Tailscale's proprietary coordination server, providing 100% data sovereignty over your network topology. It solves the "proprietary lock-in" problem for users who require a fully self-hosted, sovereign VPN solution for their homelab.

Where it fits in the stack

It sits in the Operational Playbook Layer, specifically under Infrastructure Migration. It guides the transition from a managed service to a self-hosted infrastructure component.

Typical use cases

  • Homelab Hardening: Moving your internal network control plane to hardware you own.
  • Privacy Optimization: Ensuring that no metadata about your node connections ever leaves your infrastructure.
  • Cost Management: Bypassing device limits on Tailscale's free tier by using your own server.

Strengths

  • Sovereignty: Complete control over your coordination server.
  • Cost: No per-device or per-user fees (limited only by your hardware).
  • Integration: Seamlessly integrates with Authentik for OIDC-based identity management.

Limitations

  • Operational Burden: You are responsible for the availability and security of the Headscale server.
  • Complexity: Requires managing OIDC, SSL certificates, and server backups.

When to use it

  • When you have a stable, self-hosted identity provider like Authentik.
  • When your homelab has grown beyond the scope of Tailscale's free tier or privacy policies.

When not to use it

  • If you require the "it just works" simplicity of the Tailscale SaaS.
  • If you don't have the technical expertise to manage a critical piece of networking infrastructure.

Getting started

Deployment

To begin the migration: 1. Deploy Headscale: Follow the Headscale Service guide to set up the server. 2. Back up Tailscale: Document your existing node names and ACLs. 3. Perform a Pilot: Migrate a single non-critical node first using the steps in this playbook.

Agent-Assisted Migration

Modern agents can significantly simplify the migration process. Use an early January 2027-class agent (e.g., Claude 5.6, GPT-5.6, Gemini 4.0 Ultra, Llama 4, or Qwen 3.8) integrated with Model Context Protocol (MCP 3.1 / FastMCP 3.1) to: - Translate ACLs: Convert Tailscale policy.hujson to Headscale-compatible YAML/ACL formats. - Automate Client Rollout: Script the tailscale logout and tailscale up --login-server commands across a fleet of Linux nodes via SSH using MCP-enabled terminal tools. - Validate OIDC Config: Verify the config.yaml parameters against your Authentik provider metadata.

Migration Workflow

flowchart TD
    Start[Current: Tailscale SaaS] --> Backup[Back up node names & ACLs]
    Backup --> Deploy[Deploy Headscale Server]
    Deploy --> Auth[Integrate Authentik OIDC]
    Auth --> NodeMigrate{Migrate Node}
    NodeMigrate --> Logout[tailscale logout]
    Logout --> Login[tailscale up --login-server URL]
    Login --> OIDC[OIDC Authentication]
    OIDC --> Approve[Headscale Node Approval]
    Approve --> Verify[Verify Connectivity]
    Verify --> End[Target: Self-hosted Mesh]

Migration Steps

Prerequisites

  • A functional Authentik instance for OIDC.
  • A public FQDN with valid SSL certificates (e.g., via Let's Encrypt) pointing to your Headscale server.
  • Tailscale clients installed on target nodes.

Step 1: Headscale Deployment

  1. Deploy Headscale using Docker (see Headscale Service for compose snippet).
  2. Configure config.yaml with your server_url.
  3. Integrate with Authentik for OIDC to allow family members to join easily.

Step 2: Client Migration (Manual)

On each node currently running Tailscale, perform the following:

Linux

You can use the provided migration script:

./scripts/headscale_migration.sh https://<headscale-fqdn>

MacOS / Windows
  1. Hold the Alt (or Option) key and click the Tailscale icon in the menu bar/system tray.
  2. Select Change Server....
  3. Enter your Headscale FQDN: https://<headscale-fqdn>.
  4. Follow the OIDC login flow.

Step 3: Headscale Node Approval

If not using OIDC or if a node requires manual registration: 1. Run tailscale up --login-server https://<headscale-fqdn> on the client. 2. Copy the provided URL. 3. On the Headscale server:

headscale nodes register --user <username> --key <node-key>

Step 4: Node-Specific Checklists

TrueNAS SCALE NAS
  • [x] SSH into TrueNAS.
  • [x] Run tailscale logout.
  • [x] Run tailscale up --login-server https://<headscale-fqdn>.
  • [x] Verify NAS is reachable via Tailscale IP in Headscale.
K3s Compute Node
  • [x] Ensure tailscale is running on the host.
  • [x] Run migration script or manual commands.
  • [x] Update any K3s service advertisements if using Tailscale IPs for cluster communication.
Home Assistant VM
  • [x] Use the HA Terminal & SSH add-on.
  • [x] Execute tailscale logout followed by tailscale up --login-server ....
  • [x] Re-verify HA external access if proxied through Tailscale.

Step 5: Verification

  1. List nodes on Headscale: headscale nodes list.
  2. Verify connectivity between nodes: tailscale ping <other-node-ip>.
  3. Ensure ACLs are correctly migrated if using a custom policy.hujson.

Rollback Plan

If migration fails, logout from Headscale and login back to Tailscale:

tailscale logout
tailscale up

Troubleshooting Migration Issues

  • OIDC Redirect Loops: Often caused by mismatched server_url in Headscale and redirect_uris in Authentik. Use Claude 5.6, GPT-5.6, or Gemini 4.0 Ultra to inspect the logs: docker logs headscale.
  • Node Name Conflicts: Headscale requires unique node names per user. If a migration fails due to naming, use headscale nodes rename.
  • Pre-Auth Key Expiry: If migrating headless nodes, ensure the pre-auth keys generated on the server have sufficient TTL.

CLI examples

Client Migration (Linux)

# Logout from Tailscale SaaS
tailscale logout

# Connect to a Headscale instance
tailscale up --login-server https://headscale.example.com

Headscale Server Management

# Register a node manually
headscale nodes register --user jules --key nodekey:abcdef123456

# List nodes and their status
headscale nodes list

# Create a pre-authenticated key for headless nodes
headscale preauthkeys create -u jules --expiration 24h

API examples

Querying Node Status via REST API

Agents can use the Headscale REST API to verify migration status across the fleet.

# Fetch all nodes from Headscale
curl -X GET \
  -H "Authorization: Bearer $HEADSCALE_API_KEY" \
  https://headscale.example.com/api/v1/node

Scripted Node Approval

import requests
from pydantic import BaseModel, Field, ValidationError

class NodeRegistration(BaseModel):
    user: str = Field(..., min_length=1, description="The username to register the node under")
    key: str = Field(..., pattern=r"^(nodekey|mkey):[a-f0-9]+$", description="The node key generated by the client")

def approve_node(headscale_url: str, api_key: str, user: str, node_key: str) -> bool:
    try:
        # Strict Pydantic v2 validation
        reg = NodeRegistration(user=user, key=node_key)
    except ValidationError as e:
        print(f"Validation error: {e}")
        return False

    endpoint = f"{headscale_url.rstrip('/')}/api/v1/node/register"
    headers = {"Authorization": f"Bearer {api_key}"}
    payload = reg.model_dump()
    response = requests.post(endpoint, headers=headers, json=payload)
    return response.status_code == 200

# Example usage
# approve_node("https://headscale.local", "secret_key", "jules", "nodekey:abcdef123456")

Sources / References

Contribution Metadata

  • Last reviewed: 2027-01-07
  • Confidence: high