Use raw WireGuard when you need full control over key management, want zero external dependencies, and are comfortable with manual peer configuration. Use Tailscale when you need a mesh VPN that works through NAT automatically, want a GUI for managing access policies, and value setup speed over configuration control. Both use WireGuard under the hood – Tailscale is a control plane and key distribution layer built on top of it.
Architecture Difference
Raw WireGuard is a kernel module and userspace tool. You manage keys, peer configs, and IP addressing manually. Every peer needs [Peer] blocks for every other peer it should reach. For a 10-node mesh, each node has 9 peer entries.
Tailscale runs a coordination server (their hosted one, or self-hosted Headscale) that distributes public keys and manages IP assignment from the 100.64.0.0/10 range. Nodes authenticate via SSO, get assigned a stable IP, and automatically negotiate direct connections using DERP relay servers as fallback when direct UDP is blocked.
Raw WireGuard: Full Server Setup
Install WireGuard and generate keys:
# Install on Ubuntu/Debian
sudo apt update && sudo apt install -y wireguard
# Generate server keypair
wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
chmod 600 /etc/wireguard/server.key
# Generate client keypair
wg genkey | tee /etc/wireguard/client1.key | wg pubkey > /etc/wireguard/client1.pub
Server configuration at /etc/wireguard/wg0.conf:
[Interface]
PrivateKey = <contents of server.key>
Address = 10.10.0.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
DNS = 1.1.1.1
[Peer]
PublicKey = <contents of client1.pub>
AllowedIPs = 10.10.0.2/32
Client configuration:
[Interface]
PrivateKey = <contents of client1.key>
Address = 10.10.0.2/24
DNS = 10.10.0.1
[Peer]
PublicKey = <contents of server.pub>
Endpoint = your-server-ip:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Enable and start:
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
sudo systemctl enable --now wg-quick@wg0
sudo wg show
Tailscale: Self-Hosted via Headscale
For full self-hosted control, use Headscale instead of Tailscale’s coordination servers:
# Install Headscale on your control server
wget https://github.com/juanfont/headscale/releases/latest/download/headscale_linux_amd64
chmod +x headscale_linux_amd64
sudo mv headscale_linux_amd64 /usr/local/bin/headscale
Minimal /etc/headscale/config.yaml:
server_url: https://headscale.yourdomain.com:8080
listen_addr: 0.0.0.0:8080
private_key_path: /etc/headscale/private.key
noise:
private_key_path: /etc/headscale/noise_private.key
ip_prefixes:
- 100.64.0.0/10
db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite
Register nodes against your Headscale instance:
# Create a namespace (like an org)
headscale namespaces create myorg
# Create a pre-auth key
headscale --namespace myorg preauthkeys create --reusable --expiration 24h
# On each client node
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --login-server https://headscale.yourdomain.com:8080 \
--authkey <your-preauth-key>
# Verify mesh connectivity
tailscale status
tailscale ping 100.64.0.2
Performance Comparison
Raw WireGuard runs in kernel space with no coordination layer. Measured on commodity hardware (2-core VPS, 1Gbps uplink):
Test: iperf3 between two nodes, same datacenter
Raw WireGuard: ~950 Mbps throughput, ~0.3ms added latency
Tailscale: ~820 Mbps throughput, ~1.2ms added latency (direct)
Tailscale DERP: ~400 Mbps throughput, ~15-40ms added latency (relay)
Test: iperf3 across NAT (home network to VPS)
Raw WireGuard: Requires port forwarding or dynamic DNS
Tailscale: ~680 Mbps direct, ~280 Mbps via DERP fallback
CPU overhead is comparable. Both use ChaCha20-Poly1305 encryption.
Use Case Decision Matrix
Use raw WireGuard when:
- You manage fewer than 10 static nodes with stable IPs
- You need maximum throughput and minimum latency
- You require zero external dependencies or phone-home
- Compliance requires full audit of all cryptographic material
Use Tailscale or Headscale when:
- Nodes are behind NAT (home networks, mobile clients)
- You need a mesh where every node reaches every other node
- You want SSO-based authentication with revocation
- You need ACL policies for fine-grained access control
Key Management Comparison
# WireGuard: Manual key rotation
wg genkey | tee /etc/wireguard/server-new.key | wg pubkey > /etc/wireguard/server-new.pub
sudo wg set wg0 private-key /etc/wireguard/server-new.key
# Tailscale/Headscale: Revoke a node immediately
headscale nodes list
headscale nodes delete --identifier <node-id>
# Node loses connectivity within seconds
WireGuard preshared keys add a post-quantum security layer:
[Peer]
PublicKey = <peer-pub-key>
PresharedKey = <output of: wg genpsk>
AllowedIPs = 10.10.0.2/32
Monitoring and Alerting for WireGuard Tunnels
Raw WireGuard exposes minimal observability out of the box. The wg show command gives you the last handshake timestamp and bytes transferred, which is enough to build a basic health monitor:
#!/usr/bin/env bash
# wireguard-monitor.sh — alert when a peer has not handshaked recently
INTERFACE="wg0"
MAX_STALE_SECONDS=180 # 3 minutes
wg show "$INTERFACE" latest-handshakes | while read peer_key epoch; do
now=$(date +%s)
age=$((now - epoch))
if [ "$age" -gt "$MAX_STALE_SECONDS" ]; then
echo "ALERT: peer $peer_key last handshake ${age}s ago (threshold: ${MAX_STALE_SECONDS}s)"
# Hook into your alerting system here
curl -s -X POST "$SLACK_WEBHOOK" \
-H 'Content-type: application/json' \
-d "{\"text\": \"WireGuard peer $peer_key on $INTERFACE is stale (${age}s)\"}"
fi
done
Run this from cron every two minutes. A peer that has not re-keyed in 180 seconds is either offline or experiencing connectivity problems — catching this early prevents silent tunnel failures.
For Tailscale/Headscale, query the control plane API instead:
# List node health via Headscale API
curl -s http://localhost:8080/api/v1/node \
-H "Authorization: Bearer $HEADSCALE_API_KEY" | \
python3 -c "
import json, sys, time
nodes = json.load(sys.stdin)['nodes']
now = time.time()
for n in nodes:
last_seen = n.get('lastSeen', '')
name = n['givenName']
online = n.get('online', False)
if not online:
print(f'OFFLINE: {name} (last seen: {last_seen})')
"
Firewall Configuration for WireGuard
A common misconfiguration is opening the firewall broadly on the WireGuard interface. Lock it down:
# UFW rules for WireGuard server
sudo ufw allow 51820/udp comment 'WireGuard'
sudo ufw allow in on wg0 to any port 53 comment 'DNS on VPN'
sudo ufw allow in on wg0 to any port 80,443 comment 'HTTP/S on VPN'
# Block all other traffic on the VPN interface by default
sudo ufw deny in on wg0
sudo ufw enable
# Verify
sudo ufw status verbose
If you are using WireGuard for site-to-site connectivity rather than full-tunnel VPN, restrict AllowedIPs on each peer to only the subnets they need rather than 0.0.0.0/0:
[Peer]
# Allow this peer to reach only the internal services subnet
PublicKey = <client-pub-key>
AllowedIPs = 10.10.0.2/32, 192.168.10.0/24
This split-tunnel configuration means the peer’s regular internet traffic does not traverse your VPN server, reducing bandwidth costs and giving better performance for non-private traffic.
Choosing Between Tailscale SaaS and Headscale
Tailscale’s hosted coordination service is fast to set up but involves a third-party having visibility into your network topology (node names, IPs, last-seen times). Headscale eliminates that dependency at the cost of operational overhead:
| Factor | Tailscale SaaS | Headscale (self-hosted) |
|---|---|---|
| Setup time | 5 minutes | 1-2 hours |
| Node limit (free tier) | 3 users / 100 devices | Unlimited |
| Coordination server trust | Tailscale Inc. | You |
| ACL management | Web UI | YAML file / API |
| DERP relay control | Tailscale-operated | Self-hosted or Tailscale DERP |
| MagicDNS / split-DNS | Yes | Yes (via Headscale config) |
| SSO integration | Yes (Google, GitHub, etc.) | Manual via OIDC |
For privacy-sensitive workloads where node metadata should not leave your infrastructure, Headscale is the right choice. For a development team that wants VPN access in under an hour and trusts a SaaS vendor, Tailscale’s hosted offering is difficult to beat on convenience.
Related Articles
- How to Use WireGuard for Self-Hosted VPN in 2026
- Best Self-Hosted File Sync Alternatives in 2026
- Bitwarden Self-Hosted Setup Guide
- Bitwarden vs Vaultwarden Self-Hosted: A Technical Comparison
- How To Set Up Jitsi Meet Self Hosted Encrypted Video Confere
Built by theluckystrike — More at zovo.one