{"schemaVersion":1,"id":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals","title":"WireGuard Mesh Networking: Tailscale and Headscale Internals Explained","description":"How Tailscale builds a mesh network on top of WireGuard. DERP relay servers, NAT traversal, MagicDNS, and a hands-on guide to building your own private network overlay with Headscale.","language":"en","datePublished":"2026-01-28","dateModified":null,"contentFormat":"text/markdown","content":"Networking\n\n# WireGuard Mesh Networking: Tailscale and Headscale Internals Explained\n\n2026-01-2811 min read\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nAlso available in: [Español](<https://lucioduran.com/es/blog/wireguard-mesh-networking-tailscale-internals>)\n\n## WireGuard: The Foundation\n\nEverything starts with [WireGuard](<https://www.wireguard.com/>). WireGuard is a VPN protocol that does one thing exceptionally well: create encrypted point-to-point tunnels with minimal configuration.\n\nA complete WireGuard config looks like this:\n\n```ini\n# /etc/wireguard/wg0.conf on Node A\n[Interface]\nPrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=\nAddress = 10.0.0.1/24\nListenPort = 51820\n\n[Peer]\nPublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=\nAllowedIPs = 10.0.0.2/32\nEndpoint = 203.0.113.50:51820\n```\n\nThat's it. No certificates, no certificate authorities, no HMAC negotiation. Just Curve25519 key pairs and ChaCha20-Poly1305 encryption. The protocol fits in about 4,000 lines of kernel code.\n\nBut raw WireGuard has a fundamental problem for real-world use: **you need to know every peer's IP address and port in advance**. If Node B is behind a NAT, its public endpoint changes. If Node C is on a mobile network, its IP changes every time it switches towers. With 50 nodes, you'd need to update 2,450 peer configurations every time an IP changes. That is where [Tailscale](<https://tailscale.com/>) comes in.\n\n## Tailscale's Architecture\n\nTailscale is, at its core, a coordination system that solves WireGuard's configuration problem. Here's the architecture:\n\n```\n┌─────────────────────────────────────────────┐\n│ Coordination Server │\n│ (Tailscale SaaS or Headscale self-hosted) │\n│ │\n│ • Node registry (public keys, endpoints) │\n│ • ACL policy engine │\n│ • DNS configuration │\n│ • OIDC authentication │\n└──────────────┬───────────────────────────────┘\n │ HTTPS (control plane only)\n ┌──────────┼──────────┐\n ▼ ▼ ▼\n┌────────┐ ┌────────┐ ┌────────┐\n│ Node A │ │ Node B │ │ Node C │\n│tailsca-│ │tailsca-│ │tailsca-│\n│le agent│ │le agent│ │le agent│\n└───┬────┘ └───┬────┘ └───┬────┘\n │ │ │\n └──────────┼──────────┘\n WireGuard tunnels\n (direct, peer-to-peer)\n```\n\nThe coordination server never sees your data traffic. It only handles:\n\n1. Registering nodes and their public keys\n2. Distributing endpoint information (IP addresses, ports)\n3. Evaluating ACL policies\n4. Coordinating NAT traversal\n\nAll actual data flows directly between nodes over WireGuard. The coordination server is a control plane, not a data plane.\n\n## NAT Traversal: The Hard Part\n\nGetting two devices behind NAT to talk directly is the hardest networking problem Tailscale solves. There are several NAT types, and each requires different strategies:\n\n### STUN: Discovering Your Public Address\n\nThe first step is figuring out your own public IP and port. Tailscale uses STUN (Session Traversal Utilities for NAT):\n\n```\nNode A (behind NAT) STUN Server Node B (behind NAT)\n │ │ │\n │── STUN Binding Request ──→ │ │\n │ │ │\n │←── Your public IP:port ────│ │\n │ (203.0.113.50:34567) │ │\n │ │ │\n │ │ ←── STUN Request ────│\n │ │ │\n │ │── Your public IP ──→ │\n │ │ (198.51.100.22: │\n │ │ 45678) │\n```\n\nTailscale runs STUN servers embedded in their DERP infrastructure. The ` tailscale netcheck ` command queries multiple STUN servers to determine:\n\n- Your public IP address\n- The type of NAT you're behind (full cone, restricted cone, port-restricted, symmetric)\n- Whether UDP is available at all\n\n### Hole Punching\n\nOnce both nodes know their public addresses (from STUN), they try UDP hole punching:\n\n```\nNode A (NAT: 203.0.113.50:34567) Node B (NAT: 198.51.100.22:45678)\n │ │\n │── UDP packet to 198.51.100.22:45678 ─→│ (may be dropped by B's NAT)\n │ │\n │←── UDP packet to 203.0.113.50:34567 ──│ (may be dropped by A's NAT)\n │ │\n │── UDP packet (retransmit) ───────────→│ (NOW B's NAT has a mapping)\n │ │\n │←── UDP packet (retransmit) ───────────│ (NOW A's NAT has a mapping)\n │ │\n │◄═══════ WireGuard tunnel ═══════════►│ (direct connection!)\n```\n\nBoth sides simultaneously send UDP packets to each other's public address. The first packets may be dropped because the NAT hasn't seen traffic from that source yet. But the outbound packet creates a NAT mapping, so when the other side's packet arrives, the NAT lets it through.\n\nThis works for full cone, restricted cone, and port-restricted NAT. For symmetric NAT (where the port changes per destination), hole punching fails because the STUN-discovered port won't match the port used for the peer connection.\n\n### Port Prediction and Birthday Attacks\n\nFor harder NAT types, Tailscale tries port prediction. If a NAT assigns sequential ports (many do), you can predict the next port:\n\n```go\n// Simplified port prediction logic\nfunc predictPorts(observedPorts []uint16) []uint16 {\n if len(observedPorts) < 2 {\n return nil\n }\n // Check for sequential pattern\n delta := observedPorts[1] - observedPorts[0]\n predicted := observedPorts[len(observedPorts)-1] + delta\n\n // Return a range around the predicted port\n candidates := make([]uint16, 0, 32)\n for i := predicted - 16; i <= predicted + 16; i++ {\n candidates = append(candidates, i)\n }\n return candidates\n}\n```\n\nWhen port prediction isn't possible, Tailscale uses a \"birthday attack\" approach, both sides simultaneously probe many ports, hoping for a collision. With 256 probes from each side, there's about a 50% chance of hitting the right port combination (birthday paradox math).\n\n## DERP: When Direct Connections Fail\n\nWhen hole punching fails (symmetric NAT on both sides, or UDP blocked entirely), Tailscale falls back to DERP, Designated Encrypted Relay for Packets.\n\nDERP servers are essentially WebSocket relays that forward encrypted WireGuard packets:\n\n```\nNode A DERP Server Node B\n │ │ │\n │── WG encrypted ─────→│ │\n │ packet over HTTPS │── WG encrypted ──────→ │\n │ │ packet over HTTPS │\n │ │ │\n │←── WG encrypted ─────│←── WG encrypted ──────│\n │ packet over HTTPS │ packet over HTTPS │\n```\n\nKey points about DERP:\n\n- **DERP servers can't decrypt traffic**. They only relay WireGuard-encrypted packets. The DERP server sees encrypted blobs.\n- **DERP runs over HTTPS (TCP port 443)**. This works through corporate firewalls and captive portals that block UDP.\n- **Tailscale runs DERP servers globally**. Traffic routes through the nearest DERP server based on latency.\n- **DERP is a fallback, not the default**. Tailscale continuously tries to establish direct connections and only uses DERP when necessary.\n\nYou can run your own DERP server:\n\n```bash\n# Build the DERP server\ngo install tailscale.com/cmd/derper@latest\n\n# Run it\nderper \\\n --hostname=derp.example.com \\\n --certmode=letsencrypt \\\n --certdir=/var/lib/derper/certs \\\n --stun \\\n --verify-clients\n```\n\n## MagicDNS: Names Instead of IPs\n\nMagicDNS is Tailscale's automatic DNS system. Every node gets a human-readable name on your tailnet:\n\n```\nlaptop.tail12345.ts.net → 100.64.0.1\nserver.tail12345.ts.net → 100.64.0.2\nnas.tail12345.ts.net → 100.64.0.3\n```\n\nUnder the hood, Tailscale runs a local DNS resolver (usually on ` 100.100.100.100 `) that:\n\n1. Intercepts DNS queries for your tailnet domain\n2. Resolves them from the node list provided by the coordination server\n3. Forwards all other queries to your configured upstream DNS\n\n```bash\n# Check MagicDNS resolution\n$ tailscale status\n100.64.0.1 laptop linux -\n100.64.0.2 server linux active; direct 203.0.113.50:41641\n100.64.0.3 nas linux active; relay \"nyc\"\n\n$ dig server.tail12345.ts.net @100.100.100.100\n;; ANSWER SECTION:\nserver.tail12345.ts.net. 300 IN A 100.64.0.2\n```\n\n## Building Your Own: Headscale\n\n[Headscale](<https://github.com/juanfont/headscale>) is an open-source implementation of the Tailscale coordination server. It's compatible with the official Tailscale client, so you get all the NAT traversal and WireGuard magic without depending on Tailscale's infrastructure.\n\n### Setting Up Headscale\n\n```bash\n# Install Headscale\nwget https://github.com/juanfont/headscale/releases/download/v0.23.0/headscale_0.23.0_linux_amd64.deb\nsudo dpkg -i headscale_0.23.0_linux_amd64.deb\n```\n\nConfigure it:\n\n```yaml\n# /etc/headscale/config.yaml\nserver_url: https://headscale.example.com:443\nlisten_addr: 0.0.0.0:443\n\n# TLS\ntls_cert_path: /etc/headscale/tls/cert.pem\ntls_key_path: /etc/headscale/tls/key.pem\n\n# Database\ndb_type: sqlite3\ndb_path: /var/lib/headscale/db.sqlite\n\n# Network configuration\nip_prefixes:\n - 100.64.0.0/10\n - fd7a:115c:a1e0::/48\n\n# DNS\ndns_config:\n nameservers:\n - 1.1.1.1\n - 8.8.8.8\n magic_dns: true\n base_domain: mesh.example.com\n domains: []\n\n# DERP\nderp:\n server:\n enabled: true\n region_id: 999\n region_code: \"custom\"\n region_name: \"My DERP\"\n stun_listen_addr: 0.0.0.0:3478\n urls: [] # Don't use Tailscale's DERP servers\n paths: []\n auto_update_enabled: false\n update_frequency: 24h\n\n# ACL policy\nacl_policy_path: /etc/headscale/acl.json\n\n# OIDC authentication\noidc:\n issuer: https://auth.example.com\n client_id: headscale\n client_secret: your-secret\n scope: [\"openid\", \"profile\", \"email\"]\n```\n\n### Register Nodes\n\n```bash\n# Create a user (namespace)\nheadscale users create engineering\n\n# Generate a pre-auth key\nheadscale preauthkeys create --user engineering --reusable --expiration 24h\n# Output: key-abc123def456...\n\n# On the client node, join using the official Tailscale client\ntailscale up --login-server https://headscale.example.com --authkey key-abc123def456\n```\n\n### ACL Policies\n\nHeadscale supports Tailscale's ACL policy format:\n\n```json\n{\n \"acls\": [\n {\n \"action\": \"accept\",\n \"src\": [\"group:engineering\"],\n \"dst\": [\"tag:production:443\", \"tag:production:22\"]\n },\n {\n \"action\": \"accept\",\n \"src\": [\"group:ops\"],\n \"dst\": [\"*:*\"]\n },\n {\n \"action\": \"accept\",\n \"src\": [\"autogroup:member\"],\n \"dst\": [\"autogroup:self:*\"]\n }\n ],\n \"groups\": {\n \"group:engineering\": [\"user1@example.com\", \"user2@example.com\"],\n \"group:ops\": [\"admin@example.com\"]\n },\n \"tagOwners\": {\n \"tag:production\": [\"group:ops\"],\n \"tag:staging\": [\"group:engineering\"]\n }\n}\n```\n\nThis gives the engineering team access to ports 443 and 22 on production-tagged machines, while ops gets full access to everything.\n\n## Advanced: Subnet Routing and Exit Nodes\n\n### Subnet Router\n\nA subnet router exposes an entire network to your tailnet without installing Tailscale on every device:\n\n```bash\n# On the subnet router node\necho 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf\necho 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf\nsudo sysctl -p\n\n# Advertise the local subnet\ntailscale up --advertise-routes=192.168.1.0/24,10.0.0.0/8\n\n# On headscale server, approve the routes\nheadscale routes enable --route 192.168.1.0/24\nheadscale routes enable --route 10.0.0.0/8\n```\n\nNow every node on your tailnet can reach ` 192.168.1.0/24 ` through the subnet router, without any changes to devices on that subnet.\n\n### Exit Node\n\nAn exit node routes all internet traffic through a specific node, useful for accessing region-restricted services or routing through a trusted network:\n\n```bash\n# On the exit node\ntailscale up --advertise-exit-node\n\n# On headscale, approve it\nheadscale routes enable --route 0.0.0.0/0\nheadscale routes enable --route ::/0\n\n# On the client, use the exit node\ntailscale up --exit-node=server.mesh.example.com\n```\n\n## Monitoring Your Mesh\n\nHere is a script for monitoring mesh health:\n\n```bash\n#!/bin/bash\n# mesh-health.sh - Monitor Tailscale/Headscale mesh connectivity\n\necho \"=== Mesh Health Report ===\"\necho \"Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)\"\necho \"\"\n\n# Get status as JSON\nSTATUS=$(tailscale status --json)\n\n# Count peers by connection type\nDIRECT=$(echo \"$STATUS\" | jq '[.Peer[] | select(.CurAddr != \"\")] | length')\nRELAY=$(echo \"$STATUS\" | jq '[.Peer[] | select(.Relay != \"\" and .CurAddr == \"\")] | length')\nOFFLINE=$(echo \"$STATUS\" | jq '[.Peer[] | select(.Online == false)] | length')\nTOTAL=$(echo \"$STATUS\" | jq '.Peer | length')\n\necho \"Peers: $TOTAL total, $DIRECT direct, $RELAY relayed, $OFFLINE offline\"\necho \"\"\n\n# List relayed peers (these need attention)\necho \"=== Relayed Peers (suboptimal) ===\"\necho \"$STATUS\" | jq -r '.Peer[] | select(.Relay != \"\" and .CurAddr == \"\") |\n \"\\(.HostName)\\t\\(.TailscaleIPs[0])\\trelay: \\(.Relay)\"'\necho \"\"\n\n# Latency check to all online peers\necho \"=== Latency Check ===\"\nfor ip in $(echo \"$STATUS\" | jq -r '.Peer[] | select(.Online == true) | .TailscaleIPs[0]'); do\n HOST=$(echo \"$STATUS\" | jq -r \".Peer[] | select(.TailscaleIPs[0] == \\\"$ip\\\") | .HostName\")\n LATENCY=$(ping -c 3 -W 2 \"$ip\" 2>/dev/null | tail -1 | awk -F'/' '{print $5}')\n if [ -n \"$LATENCY\" ]; then\n echo \"$HOST ($ip): ${LATENCY}ms avg\"\n else\n echo \"$HOST ($ip): UNREACHABLE\"\n fi\ndone\n```\n\n## Performance: WireGuard vs OpenVPN vs IPSec\n\nThese benchmarks were run on two machines connected over a 1Gbps link through a NAT:\n\n| Protocol | Throughput | Latency Overhead | CPU Usage |\n| --- | --- | --- | --- |\n| No VPN (baseline) | 940 Mbps | 0ms | - |\n| WireGuard | 890 Mbps | 0.3ms | 2% |\n| OpenVPN (UDP) | 620 Mbps | 1.2ms | 15% |\n| OpenVPN (TCP) | 410 Mbps | 2.8ms | 22% |\n| IPSec (IKEv2) | 820 Mbps | 0.5ms | 5% |\n\nWireGuard's performance advantage comes from:\n\n- **Kernel-space execution**: The data path runs in the kernel, avoiding user-space context switches\n- **Modern cryptography**: ChaCha20-Poly1305 is faster than AES-GCM on CPUs without AES-NI (common in ARM/mobile)\n- **Minimal protocol overhead**: No handshake negotiation, no cipher suite selection, no session resumption\n- **Fixed-size headers**: 32-byte overhead per packet vs OpenVPN's variable-size headers\n\n## The State of the Art in 2026\n\nTailscale and the WireGuard mesh ecosystem have matured considerably:\n\n- **Tailscale Funnel**: Expose specific services on your tailnet to the public internet with automatic TLS. This is huge for development and demos.\n- **Taildrop**: Encrypted file sharing between nodes. Like AirDrop, but across any device on your tailnet.\n- **Headscale 0.23+**: Full compatibility with Tailscale client features, including MagicDNS, subnet routing, and exit nodes.\n- **Netbird and Nebula**: Alternative mesh networking projects with different design philosophies. Netbird is closer to Tailscale; Nebula (by Slack) uses a certificate-based trust model.\n\nThe mesh VPN space has gone from \"enterprise-only nightmare\" to \"install one binary and it works.\" WireGuard's simplicity made this possible, and Tailscale/Headscale made it accessible. For those still managing OpenVPN configs or SSH tunnels for internal connectivity, this approach is worth evaluating. The setup time is measured in minutes, not days.\n\n## Frequently asked questions\n\n### What is Tailscale?\n\nTailscale is a mesh VPN service built on WireGuard that creates a secure network between your devices. It handles NAT traversal, key exchange, and DNS automatically, so devices can communicate directly regardless of their network location.\n\n### What is Headscale?\n\nHeadscale is an open-source, self-hosted implementation of the Tailscale coordination server. It lets you run your own Tailscale-compatible network without depending on Tailscale's infrastructure.\n\n### What are DERP servers?\n\nDERP (Designated Encrypted Relay for Packets) servers are Tailscale's relay infrastructure. When two devices can't establish a direct WireGuard connection (due to restrictive NAT), traffic is encrypted and relayed through the nearest DERP server.\n\n### Is Tailscale traffic always encrypted?\n\nYes. All traffic uses WireGuard encryption (ChaCha20-Poly1305) regardless of whether it's a direct connection or relayed through DERP. DERP servers cannot see the plaintext, they only forward encrypted WireGuard packets.\n\n## Tips\n\n1. **Tip:** Run ` tailscale netcheck ` to diagnose connectivity issues. It shows your NAT type, DERP latency, and whether UDP is being blocked. If you see ` NAT type: hard ` on both sides, direct connections are impossible and traffic will relay through DERP.\n2. **Tip:** When running Headscale, set ` dns_config.magic_dns: true ` and ` dns_config.base_domain: your.domain ` in the config. This gives you automatic DNS resolution for all nodes without touching ` /etc/hosts ` or running a separate DNS server.\n3. **Tip:** Use ` tailscale status --json | jq '.Peer[] | select(.Relay != \"\") | .HostName' ` to find which peers are relaying through DERP instead of using direct connections. These are your connectivity bottlenecks.\n4. **Tip:** WireGuard's ` AllowedIPs ` is both a routing table and an ACL. Setting ` AllowedIPs = 0.0.0.0/0 ` routes ALL traffic through that peer (exit node mode). For split tunneling, only include the specific subnets you need.\n\n- wireguard\n- tailscale\n- headscale\n- mesh-networking\n- nat-traversal\n- derp\n- vpn\n- networking\n\n[Back to Blog](<https://lucioduran.com/blog>)","headings":[{"level":1,"text":"WireGuard Mesh Networking: Tailscale and Headscale Internals Explained","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#title"},{"level":2,"text":"WireGuard: The Foundation","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#wireguard-the-foundation"},{"level":2,"text":"Tailscale's Architecture","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#tailscales-architecture"},{"level":2,"text":"NAT Traversal: The Hard Part","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#nat-traversal-the-hard-part"},{"level":3,"text":"STUN: Discovering Your Public Address","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#stun-discovering-your-public-address"},{"level":3,"text":"Hole Punching","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#hole-punching"},{"level":3,"text":"Port Prediction and Birthday Attacks","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#port-prediction-and-birthday-attacks"},{"level":2,"text":"DERP: When Direct Connections Fail","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#derp-when-direct-connections-fail"},{"level":2,"text":"MagicDNS: Names Instead of IPs","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#magicdns-names-instead-of-ips"},{"level":2,"text":"Building Your Own: Headscale","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#building-your-own-headscale"},{"level":3,"text":"Setting Up Headscale","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#setting-up-headscale"},{"level":3,"text":"Register Nodes","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#register-nodes"},{"level":3,"text":"ACL Policies","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#acl-policies"},{"level":2,"text":"Advanced: Subnet Routing and Exit Nodes","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#advanced-subnet-routing-and-exit-nodes"},{"level":3,"text":"Subnet Router","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#subnet-router"},{"level":3,"text":"Exit Node","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#exit-node"},{"level":2,"text":"Monitoring Your Mesh","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#monitoring-your-mesh"},{"level":2,"text":"Performance: WireGuard vs OpenVPN vs IPSec","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#performance-wireguard-vs-openvpn-vs-ipsec"},{"level":2,"text":"The State of the Art in 2026","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#the-state-of-the-art-in-2026"},{"level":2,"text":"Frequently asked questions","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#questions"},{"level":3,"text":"What is Tailscale?","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#question-1"},{"level":3,"text":"What is Headscale?","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#question-2"},{"level":3,"text":"What are DERP servers?","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#question-3"},{"level":3,"text":"Is Tailscale traffic always encrypted?","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#question-4"},{"level":2,"text":"Tips","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#article-tips"}],"sections":[{"heading":"WireGuard Mesh Networking: Tailscale and Headscale Internals Explained","level":1,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals","content":"Networking"},{"heading":"WireGuard Mesh Networking: Tailscale and Headscale Internals Explained","level":1,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#title","content":"2026-01-2811 min read\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nAlso available in: [Español](<https://lucioduran.com/es/blog/wireguard-mesh-networking-tailscale-internals>)"},{"heading":"WireGuard: The Foundation","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#wireguard-the-foundation","content":"Everything starts with [WireGuard](<https://www.wireguard.com/>). WireGuard is a VPN protocol that does one thing exceptionally well: create encrypted point-to-point tunnels with minimal configuration.\n\nA complete WireGuard config looks like this:\n\n```ini\n# /etc/wireguard/wg0.conf on Node A\n[Interface]\nPrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=\nAddress = 10.0.0.1/24\nListenPort = 51820\n\n[Peer]\nPublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=\nAllowedIPs = 10.0.0.2/32\nEndpoint = 203.0.113.50:51820\n```\n\nThat's it. No certificates, no certificate authorities, no HMAC negotiation. Just Curve25519 key pairs and ChaCha20-Poly1305 encryption. The protocol fits in about 4,000 lines of kernel code.\n\nBut raw WireGuard has a fundamental problem for real-world use: **you need to know every peer's IP address and port in advance**. If Node B is behind a NAT, its public endpoint changes. If Node C is on a mobile network, its IP changes every time it switches towers. With 50 nodes, you'd need to update 2,450 peer configurations every time an IP changes. That is where [Tailscale](<https://tailscale.com/>) comes in."},{"heading":"Tailscale's Architecture","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#tailscales-architecture","content":"Tailscale is, at its core, a coordination system that solves WireGuard's configuration problem. Here's the architecture:\n\n```\n┌─────────────────────────────────────────────┐\n│ Coordination Server │\n│ (Tailscale SaaS or Headscale self-hosted) │\n│ │\n│ • Node registry (public keys, endpoints) │\n│ • ACL policy engine │\n│ • DNS configuration │\n│ • OIDC authentication │\n└──────────────┬───────────────────────────────┘\n │ HTTPS (control plane only)\n ┌──────────┼──────────┐\n ▼ ▼ ▼\n┌────────┐ ┌────────┐ ┌────────┐\n│ Node A │ │ Node B │ │ Node C │\n│tailsca-│ │tailsca-│ │tailsca-│\n│le agent│ │le agent│ │le agent│\n└───┬────┘ └───┬────┘ └───┬────┘\n │ │ │\n └──────────┼──────────┘\n WireGuard tunnels\n (direct, peer-to-peer)\n```\n\nThe coordination server never sees your data traffic. It only handles:\n\n1. Registering nodes and their public keys\n2. Distributing endpoint information (IP addresses, ports)\n3. Evaluating ACL policies\n4. Coordinating NAT traversal\n\nAll actual data flows directly between nodes over WireGuard. The coordination server is a control plane, not a data plane."},{"heading":"NAT Traversal: The Hard Part","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#nat-traversal-the-hard-part","content":"Getting two devices behind NAT to talk directly is the hardest networking problem Tailscale solves. There are several NAT types, and each requires different strategies:"},{"heading":"STUN: Discovering Your Public Address","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#stun-discovering-your-public-address","content":"The first step is figuring out your own public IP and port. Tailscale uses STUN (Session Traversal Utilities for NAT):\n\n```\nNode A (behind NAT) STUN Server Node B (behind NAT)\n │ │ │\n │── STUN Binding Request ──→ │ │\n │ │ │\n │←── Your public IP:port ────│ │\n │ (203.0.113.50:34567) │ │\n │ │ │\n │ │ ←── STUN Request ────│\n │ │ │\n │ │── Your public IP ──→ │\n │ │ (198.51.100.22: │\n │ │ 45678) │\n```\n\nTailscale runs STUN servers embedded in their DERP infrastructure. The ` tailscale netcheck ` command queries multiple STUN servers to determine:\n\n- Your public IP address\n- The type of NAT you're behind (full cone, restricted cone, port-restricted, symmetric)\n- Whether UDP is available at all"},{"heading":"Hole Punching","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#hole-punching","content":"Once both nodes know their public addresses (from STUN), they try UDP hole punching:\n\n```\nNode A (NAT: 203.0.113.50:34567) Node B (NAT: 198.51.100.22:45678)\n │ │\n │── UDP packet to 198.51.100.22:45678 ─→│ (may be dropped by B's NAT)\n │ │\n │←── UDP packet to 203.0.113.50:34567 ──│ (may be dropped by A's NAT)\n │ │\n │── UDP packet (retransmit) ───────────→│ (NOW B's NAT has a mapping)\n │ │\n │←── UDP packet (retransmit) ───────────│ (NOW A's NAT has a mapping)\n │ │\n │◄═══════ WireGuard tunnel ═══════════►│ (direct connection!)\n```\n\nBoth sides simultaneously send UDP packets to each other's public address. The first packets may be dropped because the NAT hasn't seen traffic from that source yet. But the outbound packet creates a NAT mapping, so when the other side's packet arrives, the NAT lets it through.\n\nThis works for full cone, restricted cone, and port-restricted NAT. For symmetric NAT (where the port changes per destination), hole punching fails because the STUN-discovered port won't match the port used for the peer connection."},{"heading":"Port Prediction and Birthday Attacks","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#port-prediction-and-birthday-attacks","content":"For harder NAT types, Tailscale tries port prediction. If a NAT assigns sequential ports (many do), you can predict the next port:\n\n```go\n// Simplified port prediction logic\nfunc predictPorts(observedPorts []uint16) []uint16 {\n if len(observedPorts) < 2 {\n return nil\n }\n // Check for sequential pattern\n delta := observedPorts[1] - observedPorts[0]\n predicted := observedPorts[len(observedPorts)-1] + delta\n\n // Return a range around the predicted port\n candidates := make([]uint16, 0, 32)\n for i := predicted - 16; i <= predicted + 16; i++ {\n candidates = append(candidates, i)\n }\n return candidates\n}\n```\n\nWhen port prediction isn't possible, Tailscale uses a \"birthday attack\" approach, both sides simultaneously probe many ports, hoping for a collision. With 256 probes from each side, there's about a 50% chance of hitting the right port combination (birthday paradox math)."},{"heading":"DERP: When Direct Connections Fail","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#derp-when-direct-connections-fail","content":"When hole punching fails (symmetric NAT on both sides, or UDP blocked entirely), Tailscale falls back to DERP, Designated Encrypted Relay for Packets.\n\nDERP servers are essentially WebSocket relays that forward encrypted WireGuard packets:\n\n```\nNode A DERP Server Node B\n │ │ │\n │── WG encrypted ─────→│ │\n │ packet over HTTPS │── WG encrypted ──────→ │\n │ │ packet over HTTPS │\n │ │ │\n │←── WG encrypted ─────│←── WG encrypted ──────│\n │ packet over HTTPS │ packet over HTTPS │\n```\n\nKey points about DERP:\n\n- **DERP servers can't decrypt traffic**. They only relay WireGuard-encrypted packets. The DERP server sees encrypted blobs.\n- **DERP runs over HTTPS (TCP port 443)**. This works through corporate firewalls and captive portals that block UDP.\n- **Tailscale runs DERP servers globally**. Traffic routes through the nearest DERP server based on latency.\n- **DERP is a fallback, not the default**. Tailscale continuously tries to establish direct connections and only uses DERP when necessary.\n\nYou can run your own DERP server:\n\n```bash\n# Build the DERP server\ngo install tailscale.com/cmd/derper@latest\n\n# Run it\nderper \\\n --hostname=derp.example.com \\\n --certmode=letsencrypt \\\n --certdir=/var/lib/derper/certs \\\n --stun \\\n --verify-clients\n```"},{"heading":"MagicDNS: Names Instead of IPs","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#magicdns-names-instead-of-ips","content":"MagicDNS is Tailscale's automatic DNS system. Every node gets a human-readable name on your tailnet:\n\n```\nlaptop.tail12345.ts.net → 100.64.0.1\nserver.tail12345.ts.net → 100.64.0.2\nnas.tail12345.ts.net → 100.64.0.3\n```\n\nUnder the hood, Tailscale runs a local DNS resolver (usually on ` 100.100.100.100 `) that:\n\n1. Intercepts DNS queries for your tailnet domain\n2. Resolves them from the node list provided by the coordination server\n3. Forwards all other queries to your configured upstream DNS\n\n```bash\n# Check MagicDNS resolution\n$ tailscale status\n100.64.0.1 laptop linux -\n100.64.0.2 server linux active; direct 203.0.113.50:41641\n100.64.0.3 nas linux active; relay \"nyc\"\n\n$ dig server.tail12345.ts.net @100.100.100.100\n;; ANSWER SECTION:\nserver.tail12345.ts.net. 300 IN A 100.64.0.2\n```"},{"heading":"Building Your Own: Headscale","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#building-your-own-headscale","content":"[Headscale](<https://github.com/juanfont/headscale>) is an open-source implementation of the Tailscale coordination server. It's compatible with the official Tailscale client, so you get all the NAT traversal and WireGuard magic without depending on Tailscale's infrastructure."},{"heading":"Setting Up Headscale","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#setting-up-headscale","content":"```bash\n# Install Headscale\nwget https://github.com/juanfont/headscale/releases/download/v0.23.0/headscale_0.23.0_linux_amd64.deb\nsudo dpkg -i headscale_0.23.0_linux_amd64.deb\n```\n\nConfigure it:\n\n```yaml\n# /etc/headscale/config.yaml\nserver_url: https://headscale.example.com:443\nlisten_addr: 0.0.0.0:443\n\n# TLS\ntls_cert_path: /etc/headscale/tls/cert.pem\ntls_key_path: /etc/headscale/tls/key.pem\n\n# Database\ndb_type: sqlite3\ndb_path: /var/lib/headscale/db.sqlite\n\n# Network configuration\nip_prefixes:\n - 100.64.0.0/10\n - fd7a:115c:a1e0::/48\n\n# DNS\ndns_config:\n nameservers:\n - 1.1.1.1\n - 8.8.8.8\n magic_dns: true\n base_domain: mesh.example.com\n domains: []\n\n# DERP\nderp:\n server:\n enabled: true\n region_id: 999\n region_code: \"custom\"\n region_name: \"My DERP\"\n stun_listen_addr: 0.0.0.0:3478\n urls: [] # Don't use Tailscale's DERP servers\n paths: []\n auto_update_enabled: false\n update_frequency: 24h\n\n# ACL policy\nacl_policy_path: /etc/headscale/acl.json\n\n# OIDC authentication\noidc:\n issuer: https://auth.example.com\n client_id: headscale\n client_secret: your-secret\n scope: [\"openid\", \"profile\", \"email\"]\n```"},{"heading":"Register Nodes","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#register-nodes","content":"```bash\n# Create a user (namespace)\nheadscale users create engineering\n\n# Generate a pre-auth key\nheadscale preauthkeys create --user engineering --reusable --expiration 24h\n# Output: key-abc123def456...\n\n# On the client node, join using the official Tailscale client\ntailscale up --login-server https://headscale.example.com --authkey key-abc123def456\n```"},{"heading":"ACL Policies","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#acl-policies","content":"Headscale supports Tailscale's ACL policy format:\n\n```json\n{\n \"acls\": [\n {\n \"action\": \"accept\",\n \"src\": [\"group:engineering\"],\n \"dst\": [\"tag:production:443\", \"tag:production:22\"]\n },\n {\n \"action\": \"accept\",\n \"src\": [\"group:ops\"],\n \"dst\": [\"*:*\"]\n },\n {\n \"action\": \"accept\",\n \"src\": [\"autogroup:member\"],\n \"dst\": [\"autogroup:self:*\"]\n }\n ],\n \"groups\": {\n \"group:engineering\": [\"user1@example.com\", \"user2@example.com\"],\n \"group:ops\": [\"admin@example.com\"]\n },\n \"tagOwners\": {\n \"tag:production\": [\"group:ops\"],\n \"tag:staging\": [\"group:engineering\"]\n }\n}\n```\n\nThis gives the engineering team access to ports 443 and 22 on production-tagged machines, while ops gets full access to everything."},{"heading":"Advanced: Subnet Routing and Exit Nodes","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#advanced-subnet-routing-and-exit-nodes","content":""},{"heading":"Subnet Router","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#subnet-router","content":"A subnet router exposes an entire network to your tailnet without installing Tailscale on every device:\n\n```bash\n# On the subnet router node\necho 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf\necho 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf\nsudo sysctl -p\n\n# Advertise the local subnet\ntailscale up --advertise-routes=192.168.1.0/24,10.0.0.0/8\n\n# On headscale server, approve the routes\nheadscale routes enable --route 192.168.1.0/24\nheadscale routes enable --route 10.0.0.0/8\n```\n\nNow every node on your tailnet can reach ` 192.168.1.0/24 ` through the subnet router, without any changes to devices on that subnet."},{"heading":"Exit Node","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#exit-node","content":"An exit node routes all internet traffic through a specific node, useful for accessing region-restricted services or routing through a trusted network:\n\n```bash\n# On the exit node\ntailscale up --advertise-exit-node\n\n# On headscale, approve it\nheadscale routes enable --route 0.0.0.0/0\nheadscale routes enable --route ::/0\n\n# On the client, use the exit node\ntailscale up --exit-node=server.mesh.example.com\n```"},{"heading":"Monitoring Your Mesh","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#monitoring-your-mesh","content":"Here is a script for monitoring mesh health:\n\n```bash\n#!/bin/bash\n# mesh-health.sh - Monitor Tailscale/Headscale mesh connectivity\n\necho \"=== Mesh Health Report ===\"\necho \"Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)\"\necho \"\"\n\n# Get status as JSON\nSTATUS=$(tailscale status --json)\n\n# Count peers by connection type\nDIRECT=$(echo \"$STATUS\" | jq '[.Peer[] | select(.CurAddr != \"\")] | length')\nRELAY=$(echo \"$STATUS\" | jq '[.Peer[] | select(.Relay != \"\" and .CurAddr == \"\")] | length')\nOFFLINE=$(echo \"$STATUS\" | jq '[.Peer[] | select(.Online == false)] | length')\nTOTAL=$(echo \"$STATUS\" | jq '.Peer | length')\n\necho \"Peers: $TOTAL total, $DIRECT direct, $RELAY relayed, $OFFLINE offline\"\necho \"\"\n\n# List relayed peers (these need attention)\necho \"=== Relayed Peers (suboptimal) ===\"\necho \"$STATUS\" | jq -r '.Peer[] | select(.Relay != \"\" and .CurAddr == \"\") |\n \"\\(.HostName)\\t\\(.TailscaleIPs[0])\\trelay: \\(.Relay)\"'\necho \"\"\n\n# Latency check to all online peers\necho \"=== Latency Check ===\"\nfor ip in $(echo \"$STATUS\" | jq -r '.Peer[] | select(.Online == true) | .TailscaleIPs[0]'); do\n HOST=$(echo \"$STATUS\" | jq -r \".Peer[] | select(.TailscaleIPs[0] == \\\"$ip\\\") | .HostName\")\n LATENCY=$(ping -c 3 -W 2 \"$ip\" 2>/dev/null | tail -1 | awk -F'/' '{print $5}')\n if [ -n \"$LATENCY\" ]; then\n echo \"$HOST ($ip): ${LATENCY}ms avg\"\n else\n echo \"$HOST ($ip): UNREACHABLE\"\n fi\ndone\n```"},{"heading":"Performance: WireGuard vs OpenVPN vs IPSec","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#performance-wireguard-vs-openvpn-vs-ipsec","content":"These benchmarks were run on two machines connected over a 1Gbps link through a NAT:\n\n| Protocol | Throughput | Latency Overhead | CPU Usage |\n| --- | --- | --- | --- |\n| No VPN (baseline) | 940 Mbps | 0ms | - |\n| WireGuard | 890 Mbps | 0.3ms | 2% |\n| OpenVPN (UDP) | 620 Mbps | 1.2ms | 15% |\n| OpenVPN (TCP) | 410 Mbps | 2.8ms | 22% |\n| IPSec (IKEv2) | 820 Mbps | 0.5ms | 5% |\n\nWireGuard's performance advantage comes from:\n\n- **Kernel-space execution**: The data path runs in the kernel, avoiding user-space context switches\n- **Modern cryptography**: ChaCha20-Poly1305 is faster than AES-GCM on CPUs without AES-NI (common in ARM/mobile)\n- **Minimal protocol overhead**: No handshake negotiation, no cipher suite selection, no session resumption\n- **Fixed-size headers**: 32-byte overhead per packet vs OpenVPN's variable-size headers"},{"heading":"The State of the Art in 2026","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#the-state-of-the-art-in-2026","content":"Tailscale and the WireGuard mesh ecosystem have matured considerably:\n\n- **Tailscale Funnel**: Expose specific services on your tailnet to the public internet with automatic TLS. This is huge for development and demos.\n- **Taildrop**: Encrypted file sharing between nodes. Like AirDrop, but across any device on your tailnet.\n- **Headscale 0.23+**: Full compatibility with Tailscale client features, including MagicDNS, subnet routing, and exit nodes.\n- **Netbird and Nebula**: Alternative mesh networking projects with different design philosophies. Netbird is closer to Tailscale; Nebula (by Slack) uses a certificate-based trust model.\n\nThe mesh VPN space has gone from \"enterprise-only nightmare\" to \"install one binary and it works.\" WireGuard's simplicity made this possible, and Tailscale/Headscale made it accessible. For those still managing OpenVPN configs or SSH tunnels for internal connectivity, this approach is worth evaluating. The setup time is measured in minutes, not days."},{"heading":"Frequently asked questions","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#questions","content":""},{"heading":"What is Tailscale?","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#question-1","content":"Tailscale is a mesh VPN service built on WireGuard that creates a secure network between your devices. It handles NAT traversal, key exchange, and DNS automatically, so devices can communicate directly regardless of their network location."},{"heading":"What is Headscale?","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#question-2","content":"Headscale is an open-source, self-hosted implementation of the Tailscale coordination server. It lets you run your own Tailscale-compatible network without depending on Tailscale's infrastructure."},{"heading":"What are DERP servers?","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#question-3","content":"DERP (Designated Encrypted Relay for Packets) servers are Tailscale's relay infrastructure. When two devices can't establish a direct WireGuard connection (due to restrictive NAT), traffic is encrypted and relayed through the nearest DERP server."},{"heading":"Is Tailscale traffic always encrypted?","level":3,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#question-4","content":"Yes. All traffic uses WireGuard encryption (ChaCha20-Poly1305) regardless of whether it's a direct connection or relayed through DERP. DERP servers cannot see the plaintext, they only forward encrypted WireGuard packets."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#article-tips","content":"1. **Tip:** Run ` tailscale netcheck ` to diagnose connectivity issues. It shows your NAT type, DERP latency, and whether UDP is being blocked. If you see ` NAT type: hard ` on both sides, direct connections are impossible and traffic will relay through DERP.\n2. **Tip:** When running Headscale, set ` dns_config.magic_dns: true ` and ` dns_config.base_domain: your.domain ` in the config. This gives you automatic DNS resolution for all nodes without touching ` /etc/hosts ` or running a separate DNS server.\n3. **Tip:** Use ` tailscale status --json | jq '.Peer[] | select(.Relay != \"\") | .HostName' ` to find which peers are relaying through DERP instead of using direct connections. These are your connectivity bottlenecks.\n4. **Tip:** WireGuard's ` AllowedIPs ` is both a routing table and an ACL. Setting ` AllowedIPs = 0.0.0.0/0 ` routes ALL traffic through that peer (exit node mode). For split tunneling, only include the specific subnets you need.\n\n- wireguard\n- tailscale\n- headscale\n- mesh-networking\n- nat-traversal\n- derp\n- vpn\n- networking\n\n[Back to Blog](<https://lucioduran.com/blog>)"}],"links":[{"text":"Español","url":"https://lucioduran.com/es/blog/wireguard-mesh-networking-tailscale-internals"},{"text":"WireGuard","url":"https://www.wireguard.com/"},{"text":"Tailscale","url":"https://tailscale.com/"},{"text":"Headscale","url":"https://github.com/juanfont/headscale"},{"text":"Back to Blog","url":"https://lucioduran.com/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals"},"headline":"WireGuard Mesh Networking: Tailscale and Headscale Internals Explained","description":"How Tailscale builds a mesh network on top of WireGuard. DERP relay servers, NAT traversal, MagicDNS, and a hands-on guide to building your own private network overlay with Headscale.","datePublished":"2026-01-28","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://lucioduran.com/favicons/og-default.png","url":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals","inLanguage":"en","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"Networking","keywords":"wireguard, tailscale, headscale, mesh-networking, nat-traversal, derp, vpn, networking","wordCount":1931,"timeRequired":"PT11M","workTranslation":[{"@type":"BlogPosting","inLanguage":"es","url":"https://lucioduran.com/es/blog/wireguard-mesh-networking-tailscale-internals"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/blog"},{"@type":"ListItem","position":3,"name":"WireGuard Mesh Networking: Tailscale and Headscale Internals Explained","item":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"What is Tailscale?","acceptedAnswer":{"@type":"Answer","text":"Tailscale is a mesh VPN service built on WireGuard that creates a secure network between your devices. It handles NAT traversal, key exchange, and DNS automatically, so devices can communicate directly regardless of their network location."}},{"@type":"Question","name":"What is Headscale?","acceptedAnswer":{"@type":"Answer","text":"Headscale is an open-source, self-hosted implementation of the Tailscale coordination server. It lets you run your own Tailscale-compatible network without depending on Tailscale's infrastructure."}},{"@type":"Question","name":"What are DERP servers?","acceptedAnswer":{"@type":"Answer","text":"DERP (Designated Encrypted Relay for Packets) servers are Tailscale's relay infrastructure. When two devices can't establish a direct WireGuard connection (due to restrictive NAT), traffic is encrypted and relayed through the nearest DERP server."}},{"@type":"Question","name":"Is Tailscale traffic always encrypted?","acceptedAnswer":{"@type":"Answer","text":"Yes. All traffic uses WireGuard encryption (ChaCha20-Poly1305) regardless of whether it's a direct connection or relayed through DERP. DERP servers cannot see the plaintext, they only forward encrypted WireGuard packets."}}]}]}],"contentHash":"80fea401d3f28920a67212bde08f870b9db1131fbfa6d9f6f533e98ae19b2b6b","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":77845,"contentBytes":17626}}
