Privacy Tools Guide

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:

Use Tailscale or Headscale when:

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.

Built by theluckystrike — More at zovo.one