Phil Vishnevsky

Tailscale Explained: A Mesh VPN With No Open Ports

by Phil Vishnevsky · · 24 min read

  • tailscale
  • vpn
  • networking
  • homelab
  • wireguard

The first time you SSH into your home server from a coffee shop without opening a port on your router, it’s hard to see how it worked. There’s no forwarded port, no static IP, and nothing listening on the public internet. This post is about what actually happened.

Tailnet mesh diagram A mesh diagram showing devices connected through a tailnet with encrypted peer-to-peer links. Tailnet mesh (simplified) Every device connects peer-to-peer through the encrypted WireGuard mesh Tailscale coordination Laptop 100.1.2.3 Home server 100.3.4.5 Phone 100.5.6.7 Work PC 100.7.8.9 Tablet 100.10.11.12 Encrypted peer-to-peer WireGuard tunnels

What Tailscale is (and is not)

Tailscale is a mesh VPN built on WireGuard. Instead of routing all your traffic through a single VPN server, it creates direct, encrypted connections between your devices whenever possible. Think of it as a private mesh network with strong identity controls.

  • It is: a secure way to connect devices across the internet.
  • It is not: a traditional VPN for hiding your browsing from websites (though you can route traffic that way if you want).

If you’re new to VPNs entirely, start here: What Is a VPN?

The short version of how it works

  1. You install the app on devices you own.
  2. You log in with an identity provider (Google, GitHub, etc.).
  3. Tailscale brokers the connection so your devices can find each other.
  4. Devices connect directly (peer‑to‑peer) whenever possible.

Under the hood, it uses a mix of STUN and NAT traversal to discover how each device can be reached. If the network won’t allow it, Tailscale falls back to relays (DERP).

WireGuard under the hood

Tailscale uses WireGuard, which is fast, minimal, and audited. WireGuard gives you:

  • Strong modern cryptography (ChaCha20-Poly1305 for encryption, Curve25519 for key exchange)
  • Low overhead (great for laptops and mobile)
  • Simple key management
  • ~4,000 lines of code vs 100,000+ for OpenVPN — less code means fewer bugs

Here’s WireGuard’s official paper for the curious: WireGuard: Next Generation Kernel Network Tunnel

How WireGuard's cryptography works The Noise protocol handshake, step by step

How WireGuard key exchange actually works

WireGuard uses the Noise Protocol Framework, specifically the Noise_IKpsk2 handshake pattern. Here’s what that means in practice.

When two devices want to talk:

  1. Each device has a static key pair — a long-term Curve25519 public/private key. This is your device’s cryptographic identity.

  2. The initiator generates an ephemeral key pair — a temporary keypair used only for this handshake. This provides forward secrecy: even if your long-term keys get compromised later, past sessions can’t be decrypted.

  3. Diffie-Hellman happens (multiple times):

    • Initiator’s ephemeral × Responder’s static
    • Initiator’s static × Responder’s static
    • Initiator’s ephemeral × Responder’s ephemeral
  4. All those DH outputs get mixed together using HKDF to derive the actual symmetric encryption keys.

  5. Optional pre-shared key gets mixed in too (the psk2 part), adding quantum resistance if you’re paranoid about future quantum computers.

The whole handshake is 1-RTT (one round trip) and fits in a single UDP packet each way. Compare that to TLS, which needs multiple round trips and way more complexity.

WireGuard Noise_IKpsk2 Handshake (1-RTT) Initiator (Device A) Responder (Device B) generates ephemeral keypair e_i 1. Initiation msg: e_i(pub), enc{ s_i(pub) }, enc{ timestamp } DH1 = e_i x s_r DH2 = s_i x s_r generates ephemeral keypair e_r 2. Response msg: e_r(pub), enc{ empty } DH3 = e_i x e_r -> mix all DH via HKDF -> mix pre-shared key (psk2) => symmetric session keys (ChaCha20-Poly1305) 3. Encrypted transport data (forward-secret, E2E)

The Noise_IKpsk2 handshake: three Diffie-Hellman exchanges plus an optional PSK, all mixed via HKDF into forward-secret session keys — in a single round trip. Open the editable Excalidraw source.

For the full cryptographic details, see the WireGuard protocol documentation.

Control plane vs data plane (why it’s still fast)

Tailscale has a control plane that helps devices discover each other and exchange keys, but the data plane (the actual traffic) goes directly between your devices when possible. That’s why it feels fast — you’re not hair‑pinning through a central server unless you have to.

Because only metadata flows through the control plane, the coordination service never sees your actual encrypted traffic. If you self‑host with Headscale, you take over that coordination role.

Control Plane vs Data Plane Coordination server (CONTROL PLANE) keys - ACLs - peer discovery (metadata only, no traffic) Device A Device B Device C DATA PLANE: direct encrypted P2P (WireGuard, end-to-end) -- - - control plane (metadata) ----- data plane (your traffic)

The coordination server only brokers metadata (keys, ACLs, peer discovery). Your actual traffic flows directly device-to-device over the data plane. Open the editable Excalidraw source.

Tailscale vs Headscale

  • Tailscale: hosted control plane, easiest setup, polished admin UI.
  • Headscale: open‑source control plane you host yourself, more work, more control.

If you already run a homelab and want to minimize third‑party trust, Headscale is worth a look.

How Tailscale compares to traditional VPN architectures

Traditional VPNs and Tailscale solve different problems, and comparing them head-to-head only makes sense once that’s clear.

Traditional hub-and-spoke VPN (OpenVPN, IPsec, etc.)

          ┌─────────────┐
          │  VPN Server │
          │  (gateway)  │
          └──────┬──────┘
       ┌─────────┼─────────┐
       │         │         │
       ▼         ▼         ▼
    Device A  Device B  Device C
  • All traffic routes through central server — even if Device A wants to talk to Device B sitting next to it
  • Single point of failure — server goes down, everyone’s disconnected
  • Bandwidth bottleneck — server handles all traffic
  • Perimeter security model — once you’re “in,” you’re trusted
  • Complex configuration — certificates, firewall rules, port forwarding

Tailscale’s mesh architecture

    Device A ◄────────► Device B
        ▲                   ▲
        │                   │
        └───────┬───────────┘


            Device C
    
    (Control plane handles discovery,
     but data flows directly)
  • Direct peer-to-peer connections — traffic takes the shortest path
  • No single point of failure — control plane outage doesn’t kill existing connections
  • Scales horizontally — adding devices doesn’t increase central load
  • Zero-trust model — every device authenticated individually
  • Almost zero configuration — install, log in, done

The “site-to-site” problem

Traditional VPNs excel at connecting networks (your office LAN to the datacenter). Tailscale excels at connecting devices regardless of what network they’re on. Different tools, different jobs.

If you need a contractor to access one internal service without touching the rest of your network, Tailscale wins. If you need to bridge two entire subnets with legacy equipment that can’t run clients, traditional VPN (or Tailscale’s subnet routers) might be the answer.

Identity + keys (the real security story)

Every device gets a unique key pair, and access is tied to your identity provider. That’s the zero‑trust feel: instead of trusting a network, you trust who (and which device) is connecting. Under the hood it’s standard public‑key cryptography, but with sane defaults.

Tailscale also rotates keys over time, which limits the impact of a long‑lost laptop or a compromised device.

A note on identity provider trust

Here’s the uncomfortable truth: your tailnet is only as secure as your identity provider. If someone compromises your Google or GitHub account, they can add their own device to your tailnet and access everything your ACLs allow. This is the trade-off for the convenience of SSO.

Mitigations worth considering:

  • Enable MFA on your identity provider — preferably with hardware security keys, not SMS
  • Use a separate account for your homelab if you’re paranoid (a dedicated Google Workspace account, for example)
  • Enable Tailnet Lock — this requires existing nodes to sign new device keys, so even a compromised identity provider can’t unilaterally add devices

NAT traversal + DERP relays (the “how did it work?” part)

Most home networks sit behind NAT, which makes inbound connections hard. Tailscale uses NAT traversal techniques to punch through when it can. If that fails, it falls back to DERP relays (basically a secure relay network) so your devices still connect.

Relays are a fallback — not the default. You can check in the Tailscale admin panel whether a connection is direct or relayed.

How NAT hole-punching actually works (step by step)

NAT hole-punching sounds like hacker movie nonsense, but it’s actually elegant. Here’s what happens when Device A (behind NAT) wants to connect to Device B (also behind NAT):

Step 1: STUN discovery

Both devices send packets to Tailscale’s STUN servers. The STUN server sees the external IP and port that the NAT assigned, and reports it back. Now each device knows its own public-facing address.

Device A ──► NAT A ──► STUN Server

            "Your public address is 1.2.3.4:54321"

Step 2: Coordination server exchange

Tailscale’s control plane tells each device the other’s public address. Device A learns that Device B is at 5.6.7.8:12345, and vice versa.

Step 3: Simultaneous connection attempts

Here’s the magic: both devices send packets to each other at roughly the same time.

Device A (1.2.3.4:54321) ──────► Device B (5.6.7.8:12345)
Device B (5.6.7.8:12345) ──────► Device A (1.2.3.4:54321)

When Device A sends a packet out to Device B, NAT A creates a mapping: “packets from Device A going to 5.6.7.8:12345 should use external port 54321.”

When Device B’s packet arrives at NAT A, it looks like a reply to Device A’s outbound packet — so NAT A lets it through. The same happens in reverse at NAT B.

Step 4: Connection established

Both NATs have now “punched holes” for this specific peer. Traffic flows directly.

For more details, see Tailscale’s excellent How NAT traversal works blog post.

NAT Traversal: STUN Hole-Punching with DERP Fallback NAT A NAT B Device A Device B STUN server Coordination server (control) DERP relay 1. STUN: learn my public ip:port 1. STUN: learn my public ip:port 2. register + exchange peer public addrs (metadata) 3. simultaneous send = hole punch -> DIRECT P2P 4. fallback when hole-punch fails (symmetric NAT / UDP blocked): encrypted WireGuard blobs relayed - DERP can't read them

STUN discovery and simultaneous sends punch a direct path through both NATs. When symmetric NAT or a UDP-blocking firewall gets in the way, encrypted traffic falls back to a DERP relay — which still can’t read the WireGuard payload. Open the editable Excalidraw source.

DERP relay infrastructure: what’s actually happening

DERP stands for Designated Encrypted Relay for Packets (yes, really). When direct connections fail, your traffic routes through DERP servers — but here’s the key thing: DERP can’t read your traffic. It’s still WireGuard-encrypted end-to-end; DERP just forwards the opaque blobs.

Tailscale operates DERP servers worldwide:

  • North America: NYC, San Francisco, Seattle, Chicago, Dallas, Miami, Denver, Los Angeles
  • Europe: Amsterdam, Frankfurt, London, Paris, Warsaw
  • Asia-Pacific: Singapore, Sydney, Tokyo, Hong Kong, Bangalore
  • South America: São Paulo

When you run tailscale netcheck, you’ll see latency to each DERP region. The client automatically picks the fastest one.

$ tailscale netcheck

Report:
    * UDP: true
    * IPv4: yes, 1.2.3.4:54321
    * IPv6: yes, [2001:db8::1]:54321
    * MappingVariesByDestAddr: false
    * HairPinning: false
    * PortMapping: UPnP
    * Nearest DERP: San Francisco
    * DERP latency:
        - sfo: 12ms
        - lax: 18ms
        - sea: 25ms
        - nyc: 65ms
        - fra: 142ms

Tailscale Funnel: public access without the port forwarding nightmare

Here’s a feature that made me delete my Cloudflare Tunnel setup: Tailscale Funnel. It lets you expose a service on your tailnet to the public internet — no port forwarding, no dynamic DNS, no reverse proxy configuration.

How it works

Public Internet ──► Tailscale's edge ──► Your device (via DERP/direct)
    https://mybox.tail1234.ts.net
  1. Tailscale provisions a public hostname for your device (like mybox.tail1234.ts.net)
  2. They handle TLS termination with a valid certificate
  3. Traffic flows through their edge, then to your device over the encrypted tailnet
  4. Your device never needs to accept inbound connections from the internet

Setting it up

# Expose a local web server on port 8080
tailscale funnel 8080

# Or expose with a specific path
tailscale funnel --set-path=/api localhost:3000

That’s it. You get a working HTTPS URL in seconds.

When to use Funnel vs other options

ScenarioUse FunnelUse Cloudflare TunnelUse port forwarding
Quick demo/webhook testingOverkillOverkill
Production with custom domainMaybe
Self-hosted apps for friendsPain
Bandwidth-heavy (video streaming)Consider limits
Full control over edgePartial

See the Funnel documentation for the full details.

Exit nodes vs Funnel vs Cloudflare Tunnel: which do you need?

These three get confused constantly. Here’s the decision tree:

What are you trying to do?

GoalUseWhy
Route my internet traffic through a trusted locationExit NodeTunnels outbound traffic from your device through another node in your tailnet
Let someone outside my tailnet access a service I’m runningFunnel or Cloudflare TunnelCreates an inbound path from the public internet
Access my own services from my own devicesNeither — just use Tailscale directlyThat’s the default behavior

Funnel vs Cloudflare Tunnel decision:

ConsiderationFunnelCloudflare Tunnel
Setup complexityOne commandMore config, but still reasonable
Custom domainLimited (uses *.ts.net)Full control
DDoS protectionBasicCloudflare’s full stack
Bandwidth limitsYes (free tier)Generous
TLS terminationTailscale’s edgeCloudflare’s edge
Caching/CDNNoYes
Zero-trust access policiesVia Tailscale ACLsVia Cloudflare Access

My recommendation:

  • Quick demos, webhooks, sharing with a few friends → Funnel
  • Anything “production” with custom domains → Cloudflare Tunnel
  • Internal services for your own devices → Neither, just Tailscale

MagicDNS + ACLs = sane networking

Two features make Tailscale feel polished:

  • MagicDNS gives your devices friendly names (like nas.tailnet), instead of memorizing IPs, by handling DNS for your tailnet.
  • ACLs (access control lists) let you define who can access what — same idea as a classic ACL, but applied to your tailnet.

Example ACL snippet:

{
  "acls": [
    {"action": "accept", "src": ["group:admins"], "dst": ["tag:server:*"]}
  ]
}

You can also create device tags (like tag:server), then grant access to groups rather than specific devices. That makes permissions easier to reason about as your tailnet grows.

ACL patterns for homelabs

Here’s a real-world ACL structure I use for my homelab. It’s not minimal, but it’s sane:

{
  "groups": {
    "group:admins": ["user@gmail.com"],
    "group:family": ["spouse@gmail.com", "kid@gmail.com"]
  },
  "tagOwners": {
    "tag:server": ["group:admins"],
    "tag:media": ["group:admins"],
    "tag:nas": ["group:admins"]
  },
  "acls": [
    // Admins can access everything
    {"action": "accept", "src": ["group:admins"], "dst": ["*:*"]},
    
    // Family can access media servers (Jellyfin, etc.)
    {"action": "accept", "src": ["group:family"], "dst": ["tag:media:80,443,8096"]},
    
    // Family can access NAS for file sharing
    {"action": "accept", "src": ["group:family"], "dst": ["tag:nas:445,5000"]},
    
    // Servers can talk to each other (for backups, monitoring, etc.)
    {"action": "accept", "src": ["tag:server"], "dst": ["tag:server:*"]},
    
    // All devices can use the exit node
    {"action": "accept", "src": ["autogroup:members"], "dst": ["autogroup:internet:*"]}
  ]
}

Why this structure works:

  • Groups represent people, not devices — when you add a new phone, permissions follow automatically
  • Tags represent roles, not specific machines — swap your NAS hardware, just re-tag
  • Autogroups like autogroup:members and autogroup:internet reduce boilerplate
  • Explicit port lists for family access — they get Jellyfin, not SSH

Common footguns to avoid:

  • Accidentally allowing *:* to everyone — always start restrictive, then open up
  • Forgetting to tag servers — an untagged device might fall through to a permissive rule, or worse, be unreachable
  • Not approving routes — subnet routers and exit nodes need explicit approval in the admin console, not just ACLs
  • Overly complex ACLs — if you can’t explain a rule in one sentence, simplify it

Real‑world use cases

  • Remote SSH access to your server without port forwarding
  • Private media servers (Jellyfin, Plex, Audiobookshelf)
  • Family tech support without a complex router setup
  • Development environments — access your home dev server from anywhere
  • IoT device management — reach your smart home gear securely
  • Game servers — friends can join without exposing ports

Two power features worth knowing

  • Subnet routers let you reach non‑Tailscale devices on a LAN (like a printer or a smart TV) through a tagged router.
  • Exit nodes let you route all your internet traffic through a trusted device, similar to a traditional VPN but still within your tailnet.

Performance tips

  • Prefer direct connections when possible; relays add latency.
  • Put the exit node close to the users who need it.
  • If you’re self‑hosting, keep the coordination server in a low‑latency region.
  • Run tailscale netcheck to diagnose connectivity issues.
  • Check tailscale status to see which connections are direct vs relayed.

Tailscale SSH (no more shared SSH keys)

Tailscale can act as an SSH access broker, which means you authenticate with your identity provider instead of copying keys around. It’s particularly nice for teams: access can be revoked instantly without touching server configs.

See Tailscale SSH documentation for setup instructions.

File sharing: Taildrop

Tailscale includes Taildrop, a simple file transfer feature that works inside your tailnet. It’s not Dropbox, but it’s excellent for “get this file to my other device right now.”

If you want the practical walkthrough, here’s the full guide: Self‑Hosting With Tailscale

Troubleshooting common Tailscale issues

After running Tailscale for a while, you’ll hit some of these. Here’s how to fix them:

“Connection is relayed” (not direct)

Symptoms: Slow connections, tailscale status shows “relay” instead of “direct”

Diagnosis:

tailscale status
tailscale ping <device-name>
tailscale netcheck

Common causes:

  • Symmetric NAT — your router assigns different ports per destination. Try enabling UPnP/NAT-PMP on your router.
  • Firewall blocking UDP — Tailscale needs UDP port 41641 (or uses random ports). Check corporate/hotel firewalls.
  • Double NAT — you’re behind two NAT layers (ISP’s CGNAT + your router). Not much you can do except use DERP.
  • One peer behind strict firewall — if only one side is restricted, hole-punching might still fail.

Fixes:

  1. Enable UPnP on your router
  2. Try a different network (mobile hotspot) to isolate the problem
  3. If you control the network, forward UDP 41641 to one device
  4. Accept that DERP is your reality and move on

Device not showing up in tailnet

Symptoms: New device installed Tailscale but doesn’t appear in admin console

Causes:

  • Device needs to be authorized (if your tailnet requires admin approval)
  • Device is on a different tailnet (logged into wrong account)
  • Tailscale service isn’t running

Fixes:

# Check if tailscale is running
tailscale status

# See which account you're logged into
tailscale status --json | jq .Self.UserID

# Force re-authentication
tailscale logout
tailscale login

Key expiry issues

Symptoms: Device suddenly can’t connect, “key expired” in logs

Cause: By default, device keys expire after 180 days. This is a security feature.

Fixes:

  1. Re-authenticate: tailscale login
  2. Disable key expiry for specific devices in admin console (not recommended for laptops)
  3. Use --authkey with a reusable, non-expiring key for servers (create in admin console)

MagicDNS not resolving

Symptoms: Can ping by Tailscale IP but not by name

Causes:

  • MagicDNS not enabled (check admin console → DNS)
  • Local DNS resolver interfering
  • systemd-resolved conflicts (common on Linux)

Fixes for Linux:

# Check if tailscale is handling DNS
resolvectl status

# Force tailscale to take over DNS
sudo tailscale up --accept-dns=true

Subnet router not advertising routes

Symptoms: Subnet router shows as “advertising” but other devices can’t reach the subnet

Causes:

  • Routes need approval in admin console
  • IP forwarding not enabled on the router device
  • Firewall on router device blocking forwarded traffic

Fixes:

# Enable IP forwarding (Linux)
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# Check routes are approved in admin console
# Then restart tailscale
sudo tailscale up --advertise-routes=192.168.1.0/24 --reset

For more debugging, check the Tailscale troubleshooting guide and the community forum.

When Tailscale is not the right tool

  • You need anonymous browsing (use a commercial VPN like Mullvad instead)
  • You can’t install a client on a managed device
  • You want full control of the coordination server (use Headscale)
  • You need to connect networks at layer 2 (Tailscale is layer 3 only)
  • You’re bridging legacy equipment that can’t run modern clients

How to evaluate it honestly

Ask yourself:

  • Do I trust an identity‑based model more than IP‑based firewalls?
  • Do I want device‑to‑device access rather than a single VPN gateway?
  • Is my primary goal remote access, not anonymity?

If you answer “yes” to most of these, Tailscale is a strong fit.

FAQ

Is Tailscale a VPN?

Yes, in the practical sense: your devices sit on a private encrypted network. It’s just not the legacy kind — there’s no server everyone tunnels through, no port to forward, and no shared password. Each pair of devices gets its own WireGuard tunnel.

Can Tailscale see my traffic?

No. Tailscale’s coordination server is a control plane: it checks identity, hands out public keys, and tells devices how to find each other. Your actual traffic goes device-to-device over WireGuard. If even the control plane bothers you, Headscale lets you run that part yourself.

What happens when NAT hole-punching fails?

Traffic falls back to DERP — Tailscale’s relay network. Connections keep working and stay end-to-end encrypted; they just bounce through a relay instead of going direct, so they’re slower. You can see whether a connection is direct or relayed in tailscale status.

Do I need to open any ports?

No. Outbound connections from each device are enough, because the coordination server tells both sides how to reach each other and they punch holes simultaneously. Nothing has to listen on the public internet unless you deliberately use Funnel to expose a service.

Further reading

Summary

Tailscale is a VPN in the most practical sense — it makes your devices feel like they’re on the same private network, without the pain of legacy VPN setups. If your goal is private remote access rather than “hide my browsing,” it’s one of the cleanest solutions available.

Next up: Self‑Hosting With Tailscale

Saved you an afternoon? You can buy me a coffee. ko-fi.com/zenatron ↗