Tailscale vs Headscale: Self-Hosted VPN Comparison
Tailscale and Headscale both promise a mesh VPN that feels like magic, but they sit on opposite ends of the control spectrum. Tailscale is a fully‑managed service that hides the complexity behind a sleek UI, while Headscale is the open‑source counterpart you can run on your own hardware. In this post we’ll dissect their architectures, walk through real deployment steps, and help you decide which self‑hosted model fits your workflow.
What is Tailscale?
Tailscale builds on WireGuard, a modern UDP‑based VPN protocol known for its speed and minimal code footprint. By leveraging WireGuard’s cryptographic primitives, Tailscale creates a peer‑to‑peer mesh where each device talks directly to every other device it needs to reach.
Beyond the wire, Tailscale runs a coordination layer called the “control server.” This cloud service handles device authentication, key exchange, and NAT traversal. You sign in with an OAuth provider (Google, Microsoft, GitHub, etc.), and Tailscale automatically provisions a Tailnet—a private network namespace unique to your organization.
Key Features
- Zero‑config networking: Devices appear as if they’re on the same LAN, no manual routing required.
- ACLs and access control: Fine‑grained policies expressed in JSON let you lock down which services can talk to each other.
- Magic DNS: Hostnames resolve automatically across the Tailnet without a separate DNS server.
- Device sharing: Invite external collaborators with a single click, and they instantly become part of your mesh.
What is Headscale?
Headscale is the open‑source, self‑hosted implementation of the Tailscale control plane. It mimics the API surface of Tailscale’s cloud service, allowing you to run the coordination logic on premises, in a private cloud, or even on a Raspberry Pi.
Because Headscale talks the same protocol as the official Tailscale client, you can keep using the familiar tailscale binary on your devices. The only difference is that the client points to your own Headscale server instead of login.tailscale.com.
Why Choose Headscale?
- Data sovereignty: All authentication events and ACLs stay inside your network.
- Cost control: No per‑user subscription fees; you only pay for the infrastructure you already own.
- Customization: Extend the API, add custom login flows, or integrate with internal identity providers.
- Learning opportunity: Running the control plane yourself deepens your understanding of mesh networking.
Architecture Comparison
Both solutions share the same client‑side stack: the tailscale daemon (a thin wrapper around WireGuard) and the tailscaled service that handles key management. The divergence happens in the control plane.
| Component | Tailscale (Managed) | Headscale (Self‑Hosted) |
|---|---|---|
| Control Server | Hosted by Tailscale (highly available, global) | Your own VM/container, single point unless you add HA |
| Authentication | OAuth via Google, Microsoft, GitHub, SSO (SAML/OIDC) | OAuth2/OIDC or pre‑shared keys; you configure the provider |
| ACL Management | Web UI + JSON editor, versioned | JSON file on disk, reload on change |
| Metrics & Auditing | Built‑in dashboards, export to SumoLogic | Prometheus exporter, logs to stdout |
In practice, the choice often boils down to trust vs. convenience. If you’re comfortable trusting a third‑party with authentication metadata, the managed service saves you time. If compliance mandates that no user data ever leaves your data center, Headscale becomes the logical answer.
Deploying Headscale with Docker Compose
Below is a minimal docker-compose.yml that spins up Headscale, a PostgreSQL backend, and an optional nginx reverse proxy for TLS termination. This setup works on any Linux host with Docker installed.
version: "3.8"
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: headscale
POSTGRES_PASSWORD: secretpassword
POSTGRES_DB: headscale
volumes:
- pgdata:/var/lib/postgresql/data
headscale:
image: headscale/headscale:0.22.0
depends_on:
- db
command: serve
environment:
HEADSCALE_DATABASE_TYPE: postgres
HEADSCALE_DATABASE_URL: postgres://headscale:secretpassword@db:5432/headscale?sslmode=disable
HEADSCALE_LOG_LEVEL: info
ports:
- "8080:8080"
volumes:
- ./config:/etc/headscale
restart: unless-stopped
nginx:
image: nginx:alpine
depends_on:
- headscale
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
restart: unless-stopped
volumes:
pgdata:
Save this file as docker-compose.yml, create a config/config.yaml for Headscale (see next section), and run docker compose up -d. Your control plane will be reachable at https://<your‑host>/.
Headscale Configuration Basics
The config.yaml file tells Headscale how to authenticate users and where to store ACLs. Below is a concise example that uses GitHub OAuth and stores ACLs in acl.json:
server_url: "https://vpn.example.com"
listen_addr: ":8080"
log_level: "info"
oauth:
provider: "github"
client_id: "YOUR_GITHUB_CLIENT_ID"
client_secret: "YOUR_GITHUB_CLIENT_SECRET"
redirect_url: "https://vpn.example.com/oauth/callback"
acl_policy_path: "/etc/headscale/acl.json"
database:
type: "postgres"
url: "postgres://headscale:secretpassword@db:5432/headscale?sslmode=disable"
Replace the placeholders with your actual GitHub OAuth app credentials. After the container restarts, navigate to https://vpn.example.com to complete the OAuth flow.
Pro tip: For production, terminate TLS at a dedicated reverse proxy (like Caddy or Traefik) and let Headscale listen on plain HTTP internally. This isolates TLS handling from the Go binary and simplifies certificate rotation.
Registering Devices to Your Headscale Tailnet
On each client machine, install the official Tailscale client. The binary works the same regardless of the control plane.
# Debian/Ubuntu
curl -fsSL https://tailscale.com/install.sh | sh
# macOS (brew)
brew install tailscale
Instead of logging into login.tailscale.com, point the client at your Headscale server:
sudo tailscale up --login-server https://vpn.example.com --authkey <YOUR_AUTH_KEY>
You can generate an auth key via the Headscale CLI:
docker exec -it headscale_headscale_1 headscale preauthkeys create --reusable --expiration 30d
The resulting key can be distributed to devices via a secure channel (e.g., Ansible vault or a secret manager). Once a device authenticates, it appears in the Tailnet and receives a 100.x.x.x IP address automatically.
Using Magic DNS
Enable Magic DNS in your config.yaml to resolve device hostnames without an external DNS server:
magic_dns:
enabled: true
base_domain: "example.internal"
Now a device named dev‑frontend becomes reachable at dev-frontend.example.internal from any other node in the same Tailnet.
Real‑World Use Cases
1. Remote Development Environments
Developers often spin up cloud VMs for heavy‑weight builds. With Headscale, each VM automatically joins the same mesh as the developer’s laptop, allowing ssh and docker commands to work as if the VM were on the local LAN.
2. Zero‑Trust Service Mesh for Microservices
Instead of a traditional VPN gateway, each microservice runs a lightweight tailscaled process. Services discover each other via Magic DNS and enforce ACLs at the network layer, reducing the attack surface.
3. Secure Access to Legacy Appliances
Many on‑prem devices (e.g., industrial PLCs) lack modern authentication. By deploying a small Linux box with tailscaled next to the appliance, you expose the appliance’s IP over the mesh without opening any inbound ports on your firewall.
Performance and Scaling Considerations
WireGuard’s performance is largely dictated by the underlying network stack, not the control plane. However, the control server does affect device onboarding latency and NAT traversal success rates.
- Latency: Managed Tailscale runs in multiple AWS regions, guaranteeing sub‑second response times worldwide. A single‑node Headscale instance in a data center may add a few extra seconds for devices far away.
- Scalability: Headscale’s PostgreSQL backend can handle thousands of nodes, but you’ll need to monitor connection pool limits and index the
nodestable for large deployments. - High Availability: Tailscale offers built‑in HA. For Headscale, you can run multiple instances behind a load balancer and share the same PostgreSQL database, but you must enable the
--disable-ephemeral-nodeflag to avoid duplicate key generation.
Sample Prometheus Exporter Setup
Headscale ships with a Prometheus endpoint. Add the following to your docker-compose.yml to scrape metrics:
headscale:
# existing definition …
environment:
- HEADSCALE_METRICS=true
ports:
- "8080:8080"
- "9090:9090" # metrics endpoint
Then configure Prometheus:
scrape_configs:
- job_name: 'headscale'
static_configs:
- targets: ['headscale:9090']
Security Best Practices
Both Tailscale and Headscale inherit WireGuard’s strong cryptography, but operational security still matters.
- Rotate Auth Keys Regularly: Use short‑lived pre‑auth keys (e.g., 30 days) and automate revocation with a CI pipeline.
- Restrict API Access: Headscale’s REST endpoints should be bound to a private network or protected by a firewall.
- Enable Multi‑Factor Authentication (MFA): When using an OAuth provider, enforce MFA at the IdP level.
- Audit ACL Changes: Store
acl.jsonin a Git repository and require pull‑request approvals for modifications.
Pro tip: Combine Headscale with
tailscale serveto expose internal HTTP services over TLS, eliminating the need for separate reverse proxies inside the mesh.
Cost Comparison
Tailscale’s free tier supports up to 20 devices with basic ACLs, which is sufficient for hobby projects. The paid “Teams” plan starts at $6 per user per month, adding SSO, audit logs, and larger device limits.
Headscale eliminates subscription fees entirely, but you must account for the underlying compute and storage. A modest 2‑CPU VM with 2 GB RAM (≈$10/month on most cloud providers) can comfortably run a Tailnet of a few hundred nodes.
If you already have idle infrastructure (e.g., a Kubernetes cluster), Headscale’s incremental cost may be negligible, making it attractive for startups and enterprises with strict budgets.
Choosing the Right Solution
Ask yourself these three questions:
- Do you need absolute data control? If compliance or internal policies forbid third‑party credential storage, Headscale wins.
- How much operational overhead can you tolerate? Managed Tailscale removes the need to patch, monitor, and back up the control plane.
- What’s your scaling horizon? For < 100 devices, both options are comparable. Beyond that, the managed service’s global HA may save you engineering time.
In many cases, teams start with the managed service for rapid onboarding, then transition to Headscale once they’ve validated the mesh concept and need tighter governance.
Conclusion
Tailscale and Headscale share the same WireGuard‑powered DNA, yet they diverge dramatically in how they handle control, authentication, and cost. Tailscale shines for teams that prioritize speed, global availability, and a polished UI, while Headscale empowers organizations that demand full sovereignty, custom integrations, or zero‑cost scaling.
By understanding the architectural trade‑offs, deploying a simple Docker‑Compose stack, and following the security and performance tips outlined above, you can confidently pick the VPN mesh that aligns with your technical and business goals. Happy meshing!