Want a private, zero-trust mesh VPN just like Tailscale – but 100% self-hosted, no cloud dependency, unlimited devices, and full privacy control?

The perfect combo is official Tailscale clients (on every platform) + Headscale – the leading open-source, self-hosted alternative to Tailscale’s coordination server.

You get the exact same magical experience: automatic NAT traversal, MagicDNS, ACLs, exit nodes, subnet routers, Tailscale SSH – but all coordination happens on your own server.

Key Benefits of Self-Hosting with Headscale

  • Unlimited devices & users – no Tailscale subscription
  • Complete data sovereignty & privacy (no third-party knows who connects to what)
  • Identical UX on Windows, macOS, Linux, iOS, Android, iPadOS
  • Great for homelabs, remote teams, small businesses, IoT fleets
  • Free forever (only VPS cost ~$5–10/mo)

This 2026-updated guide uses the modern, recommended stack: Docker Compose + Caddy (automatic HTTPS).

Architecture Overview

1. Prerequisites

  • VPS with Ubuntu 22.04/24.04 (DigitalOcean, Vultr) – 1–2 CPU / 2 GB RAM is plenty
  • Domain or subdomain (e.g., headscale.addrom.com) with A record pointing to VPS public IP
  • Open ports: 80 (Let’s Encrypt) + 443 (HTTPS + QUIC)
  • Docker & Docker Compose installed
sudo apt update && sudo apt install docker.io docker-compose-plugin -y
  • Basic terminal knowledge

2. Install Headscale + Caddy with Docker Compose

mkdir ~/headscale && cd ~/headscale
mkdir config lib

docker-compose.yml

services:
  headscale:
    image: headscale/headscale:stable
    container_name: headscale
    volumes:
      - ./config:/etc/headscale
      - ./lib:/var/lib/headscale
    restart: unless-stopped
    command: serve

  caddy:
    image: caddy:latest
    container_name: caddy
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    restart: unless-stopped
    depends_on:
      - headscale

volumes:
  caddy_data:
  caddy_config:

Caddyfile (auto HTTPS)

https://headscale.addrom.com {
    reverse_proxy headscale:8080
}

Download example config and customize:

cd config
curl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml

Key changes in config.yaml

server_url: https://headscale.addrom.com
listen_addr: 0.0.0.0:8080
grpc_listen_addr: 0.0.0.0:50443

tls_letsencrypt_hostname: headscale.addrom.com
tls_letsencrypt_challenge_type: HTTP-01

dns:
  magic_dns: true
  base_domain: tailnet.addrom.com   # different from server_url
  nameservers:
    global:
      - 1.1.1.1
      - 8.8.8.8

Start everything:

cd ~/headscale
docker compose up -d

Verify:

curl -I https://headscale.addrom.com/health   # → 200 OK

3. Create Users & Pre-Auth Keys

docker exec -it headscale sh
headscale users create myuser   # or yourname, teamname, etc.
headscale preauthkeys create --user myuser --expiration 24h --reusable

Copy the key (hskey-auth-...) – it’s one-time-use or reusable depending on flag.

4. Connect Devices Using Official Tailscale Clients

Linux example

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up \
  --login-server=https://headscale.addrom.com \
  --authkey=hskey-auth-...

Windows / macOS / mobile

  • Install Tailscale app from official site / stores
  • In app: Use custom login server → https://headscale.addrom.com
  • Or paste auth key directly

Check nodes:

docker exec headscale headscale nodes list

Headscale Web UI (Highly Recommended)

Add this service to docker-compose.yml:

  headscale-ui:
    image: ghcr.io/gurucomputing/headscale-ui:latest
    container_name: headscale-ui
    restart: unless-stopped

Update Caddyfile:

https://headscale.addrom.com {
    reverse_proxy /web* http://headscale-ui:8080
    reverse_proxy headscale:8080
}

Access: https://headscale.addrom.com/web

5. Advanced Features (Just Like Official Tailscale)

  • Exit Node: tailscale up --advertise-exit-node → approve & use via app
  • Subnet Router: tailscale up --advertise-routes=192.168.1.0/24headscale routes enable --route ...
  • MagicDNS: Access devices as laptop.tailnet.addrom.com
  • ACLs: Edit acl.hujson for fine-grained access

Troubleshooting Tips

  • Connection fails? Double-check HTTPS, DNS, firewall (ufw allow 80,443)
  • MagicDNS not resolving? Ensure base_domain differs from server_url
  • Update: docker compose pull && docker compose up -d

Final Thoughts

Self-hosting Tailscale-style VPN with Headscale gives you enterprise-grade mesh networking without vendor lock-in or privacy concerns. It’s rock-solid for homelabs, remote work, and small teams.

Official resources:

Have you set up your own Headscale instance yet? Drop your first username or any issues in the comments – happy to help!

You may also like

Subscribe
Notify of
guest

0 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments