Skip to content
SumGuy's Ramblings
Go back

Tailscale Deep Dive: Mesh VPN That Just Works (and Why That's Suspicious)

WireGuard Is Perfect, Actually — If You Enjoy Configuration Suffering

Let me be clear: WireGuard is technically excellent. The codebase is tiny, the cryptography is modern, the performance is superb. Setting it up involves generating keypairs on every device, distributing public keys, defining allowed IPs, configuring endpoints, setting up routing, handling NAT traversal yourself, and repeating this process every time you add a new device or change your home IP.

If you have ten devices and enjoy spending your Saturday afternoons in a text editor, it’s great.

Tailscale wraps WireGuard in a coordination layer that handles all of that automatically. Every device gets a 100.x.x.x address that works regardless of where either device is. No port forwarding. No key distribution. No NAT headaches. It just works, which is either a blessing or deeply suspicious depending on your paranoia level.

This article assumes you’ve done tailscale up and thought “cool, what else can this do?” The answer is: quite a bit.

The Control Plane vs. The Data Plane

First, a crucial distinction for understanding Tailscale’s architecture.

Data plane: The actual WireGuard tunnels between your devices. Traffic flows directly peer-to-peer (or through relay servers when direct connections aren’t possible). Tailscale never sees your actual traffic. This is verifiable — it’s just WireGuard.

Control plane: The coordination server that distributes public keys, manages device registrations, enforces ACLs, and keeps the network topology synchronized. This runs at login.tailscale.com by default. Tailscale does see metadata about your devices here.

The split matters. When people worry about Tailscale “seeing their traffic,” they’re confusing the two. When people worry about Tailscale account lockout or service going down, the control plane is the concern.

Headscale (covered later) replaces the control plane. The data plane stays WireGuard regardless.

ACL Policies: Actually Control Who Can Talk to What

By default, all devices in your tailnet can reach all other devices on all ports. This is fine for personal use and increasingly uncomfortable as your tailnet grows.

ACL policies live in the Access Controls section of the Tailscale admin console. The format is HuJSON — JSON with comments allowed, which is surprisingly civilized.

Basic ACL Structure

{
  // Define groups for easier management
  "groups": {
    "group:admin": ["user@example.com"],
    "group:homelab": ["user@example.com"],
    "group:family": ["user@example.com", "spouse@example.com"]
  },

  // Tag-based ownership
  "tagOwners": {
    "tag:server": ["group:admin"],
    "tag:media": ["group:admin"],
    "tag:printer": ["group:admin"]
  },

  // ACL rules: source -> destination:port
  "acls": [
    // Admins can reach everything
    {
      "action": "accept",
      "src": ["group:admin"],
      "dst": ["*:*"]
    },

    // Family can reach media server (Plex port)
    {
      "action": "accept",
      "src": ["group:family"],
      "dst": ["tag:media:32400"]
    },

    // Nobody can reach printers except admins (already covered above)
    // Default deny covers everything else
  ]
}

Without an explicit accept rule, traffic is denied. The default ACL that allows everything is a "*:*" -> "*:*" rule that you can remove.

Tagging Devices

Tags are assigned per-device and let you write ACLs against device categories rather than specific users:

# Apply tags when registering
tailscale up --advertise-tags=tag:server

# Or set in admin console per device

Tags are especially useful for servers that shouldn’t be associated with a specific user account — if you rotate the user, you don’t have to rewrite all your ACLs.

Testing ACLs Before You Lock Yourself Out

The admin console has a policy tester. Use it. Locking yourself out of your own tailnet is embarrassing and fixable, but annoying.

Subnet Routers: Bring Your Whole LAN into Tailscale

Subnet routing lets a Tailscale device advertise routes for non-Tailscale devices on its local network. Your home server becomes a gateway into your entire home LAN.

On the device that will be the subnet router:

# Advertise your home LAN
tailscale up --advertise-routes=192.168.1.0/24

# Or advertise multiple subnets
tailscale up --advertise-routes=192.168.1.0/24,10.0.0.0/24

In the Tailscale admin console: Go to the machine, click the three dots, and approve the subnet routes. This is a deliberate speed bump — you don’t want accidental subnet advertisements becoming security holes.

On client devices: Enable subnet routing:

# Accept routes from subnet routers
tailscale up --accept-routes

Now your laptop (wherever it is) can reach 192.168.1.x devices that have never heard of Tailscale. Your old NAS, your printer, your router’s admin interface — all accessible as if you were home.

ACL consideration for subnets:

{
  "acls": [
    {
      "action": "accept",
      "src": ["group:admin"],
      "dst": ["192.168.1.0/24:*"]
    }
  ]
}

You can write ACL rules targeting subnet CIDR ranges, which lets you control who gets subnet access at the policy level.

Exit Nodes: Route All Your Traffic Through Home

Exit nodes turn your home server into a VPN exit point. All your internet traffic routes through your home connection, appearing to the outside world as coming from your home IP.

On the device that will be the exit node:

tailscale up --advertise-exit-node

Approve it in the admin console.

On the client device:

# Use a specific device as exit node
tailscale up --exit-node=HOSTNAME_OR_IP

# Or by Tailscale IP
tailscale up --exit-node=100.x.x.x

# Accept DNS from exit node too (recommended)
tailscale up --exit-node=100.x.x.x --exit-node-allow-lan-access

Use cases: coffee shop Wi-Fi you don’t trust, bypassing regional content restrictions, appearing to services as your home IP.

Important: Your home internet connection becomes the bottleneck. If your home upload is 50Mbps, that’s your throughput cap when using it as an exit node.

Tailscale SSH: SSH Without Port 22 Open

Tailscale SSH lets devices on your tailnet SSH into each other through the Tailscale connection, replacing traditional SSH entirely for devices in your network.

# Enable Tailscale SSH on the server
tailscale up --ssh

# SSH from any Tailscale device (no keys needed if using identity auth)
ssh user@100.x.x.x
# or using MagicDNS
ssh user@myserver

ACL rules control SSH access:

{
  "ssh": [
    {
      "action": "accept",
      "src": ["group:admin"],
      "dst": ["tag:server"],
      "users": ["root", "ubuntu"]
    },
    {
      "action": "check",
      "src": ["*"],
      "dst": ["tag:server"],
      "users": ["*"]
    }
  ]
}

The "check" action requires identity verification (re-auth) before access. Useful for sensitive servers.

MagicDNS: Hostname Resolution Across Your Tailnet

MagicDNS assigns every device in your tailnet a DNS name (devicename.tailnetname.ts.net) resolvable from anywhere in the tailnet. No more remembering 100.x.x.x addresses.

Enable it in the admin console under DNS → Enable MagicDNS.

You can also add custom DNS entries:

{
  "dnsConfig": {
    "routes": {
      "internal.example.com": ["100.x.x.x"]
    },
    "nameservers": ["1.1.1.1", "8.8.8.8"]
  }
}

Split DNS works too — route internal domains through your home DNS server:

{
  "dnsConfig": {
    "routes": {
      "home.arpa": ["192.168.1.1"]
    }
  }
}

Headscale: Own Your Control Plane

Headscale is an open-source, self-hosted implementation of the Tailscale coordination server. You run the control plane. Your devices register with your Headscale instance instead of Tailscale’s servers.

What you get: Full control, no external dependency, no data leaving your infrastructure, works even if Tailscale the company goes down or changes its pricing.

What you trade: Some Tailscale features (Tailscale SSH ACLs, some MagicDNS features, funnel/serve), self-service management overhead, and the polished admin console.

Running Headscale with Docker

# docker-compose.yml
version: "3.8"

services:
  headscale:
    image: headscale/headscale:latest
    container_name: headscale
    volumes:
      - ./config:/etc/headscale
      - ./data:/var/lib/headscale
    ports:
      - "8080:8080"
      - "9090:9090"  # Metrics
    command: headscale serve
    restart: unless-stopped
# config/config.yaml
server_url: https://headscale.yourdomain.com
listen_addr: 0.0.0.0:8080
metrics_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

db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite

dns_config:
  nameservers:
    - 1.1.1.1
  magic_dns: true
  base_domain: headscale.internal

log:
  level: info

Put this behind a reverse proxy (Traefik, nginx) with TLS. Headscale requires HTTPS.

Creating Users and Nodes

# Create a user (namespace in older Headscale docs)
docker exec headscale headscale users create myuser

# Generate a pre-auth key for device registration
docker exec headscale headscale preauthkeys create --user myuser

# List nodes
docker exec headscale headscale nodes list

# Show routes (subnet advertisements)
docker exec headscale headscale routes list

# Enable a subnet route
docker exec headscale headscale routes enable -r ROUTE_ID

Connecting Clients to Headscale

# On any Tailscale client (Linux, macOS, Windows, mobile)
tailscale up --login-server=https://headscale.yourdomain.com

The client will give you a URL to complete registration, or you can use pre-auth keys for automated provisioning:

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

Tailscale vs Headscale: When to Use Which

FeatureTailscale (Cloud)Headscale (Self-hosted)
Setup complexityMinimalModerate
Control plane ownershipTailscale Inc.You
Admin UIPolished web consoleCLI only (unofficial UIs exist)
Tailscale SSH ACLsFull supportLimited
Funnel/ServeYesNo
iOS/Android clientsYesYes (same clients)
Free tierUp to 100 devicesUnlimited
Uptime dependencyTailscale’s serversYour server

The honest answer: for personal use and small home labs, Tailscale cloud is fine. The free tier covers most use cases, the data plane is still WireGuard, and the polish is worth the control plane tradeoff.

For teams with compliance requirements, distrust of third-party coordination servers, or specific audit needs, Headscale is worth the operational overhead.

You can also migrate — Headscale is compatible with the Tailscale client, so you can switch control planes without replacing client software.


Share this post on:

Previous Post
Plausible vs Umami: Privacy-Friendly Analytics That Won't Creep Out Your Users
Next Post
Podman Quadlets: Running Containers Without the Docker Daemon (or Your Sanity)