Key Takeaways
- Kernel-native speed: WireGuard runs as a kernel module — no userspace overhead. On a Hetzner CX22, it saturates the 1Gbps NIC at 950+ Mbps, compared to ~400Mbps for OpenVPN on the same hardware.
- Key-based auth only: No passwords, no CAs, no certificates. One
wg genkeyper peer, exchange public keys, done. The simplicity eliminates entire classes of credential management vulnerabilities. AllowedIPsis a routing table:AllowedIPs = 0.0.0.0/0routes all traffic through the VPN (full tunnel).AllowedIPs = 10.0.0.0/8routes only private traffic (split tunnel). WireGuard enforces this — only packets from allowed ranges are accepted.- SovereignScore 99/100: Your VPN server, your keys, your infrastructure. No NordVPN, no Mullvad, no trust in a third party’s logging policies. One point deducted for the optional DNS resolver being a public resolver — use a local
unboundinstance for 100/100.
Introduction
Direct Answer: How do I set up a WireGuard VPN server on Ubuntu 24.04 in 2026?
Install WireGuard tools with sudo apt-get install -y wireguard. Generate a server key pair: wg genkey | sudo tee /etc/wireguard/private.key | wg pubkey | sudo tee /etc/wireguard/public.key. Create /etc/wireguard/wg0.conf with [Interface] specifying Address = 10.10.0.1/24, PrivateKey = <server private key>, ListenPort = 51820, plus PostUp/PostDown iptables rules for NAT. Add a [Peer] block for each client with their public key and AllowedIPs = 10.10.0.2/32. Enable IP forwarding with sudo sysctl -w net.ipv4.ip_forward=1. Start the tunnel with sudo wg-quick up wg0 and enable it at boot with sudo systemctl enable wg-quick@wg0. On the client, generate a key pair, create a matching wg0.conf with the server’s public key and Endpoint = SERVER_IP:51820, and run sudo wg-quick up wg0. The entire setup takes under 25 minutes on a fresh Ubuntu 24.04 server.
“A VPN you control is not just a privacy tool — it is the sovereign alternative to trusting a third-party service that has its own logging policies, legal jurisdiction, and business model built around your traffic data.”
Part 1: Server Setup
# ── ON THE SERVER ─────────────────────────────────────────────────────────
# WireGuard kernel module is included in Ubuntu 24.04 — only tools needed
sudo apt-get install -y wireguard wireguard-tools
# Enable IP forwarding (required for routing client traffic to internet)
sudo tee -a /etc/sysctl.conf << 'EOF'
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
EOF
sudo sysctl -p
Expected output:
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
# Generate server keypair
sudo bash -c 'wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key'
sudo chmod 600 /etc/wireguard/private.key
# View the keys (you'll need the public key for clients)
SERVER_PRIVATE=$(sudo cat /etc/wireguard/private.key)
SERVER_PUBLIC=$(sudo cat /etc/wireguard/public.key)
echo "Server public key: $SERVER_PUBLIC"
Expected output:
Server public key: 4Hjk9MNXv3T2xLqP8wZRyVe0sU1oI5nB6gAcKfWmDpE=
# Find your public network interface name
ip route show default | awk '{print $5}' | head -1
Expected output:
eth0
# Create server WireGuard config
SERVER_PRIVATE=$(sudo cat /etc/wireguard/private.key)
PUBLIC_IF=$(ip route show default | awk '{print $5}' | head -1)
sudo tee /etc/wireguard/wg0.conf << EOF
[Interface]
# VPN server IP on the WireGuard network
Address = 10.10.0.1/24
# Server's private key
PrivateKey = ${SERVER_PRIVATE}
# WireGuard listens on UDP port 51820
ListenPort = 51820
# DNS server for connected clients
DNS = 9.9.9.9
# NAT: forward client traffic to internet via the public interface
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ${PUBLIC_IF} -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ${PUBLIC_IF} -j MASQUERADE
# ── PEERS (add one [Peer] block per client) ────────────────────────────────
# [Peer]
# PublicKey = <client public key>
# AllowedIPs = 10.10.0.2/32 # Full tunnel: 0.0.0.0/0,::/0
EOF
sudo chmod 600 /etc/wireguard/wg0.conf
sudo cat /etc/wireguard/wg0.conf | grep -v PrivateKey
Expected output (PrivateKey redacted):
[Interface]
Address = 10.10.0.1/24
PrivateKey = [REDACTED]
ListenPort = 51820
DNS = 9.9.9.9
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Open firewall for WireGuard (UDP 51820) and allow forwarded traffic
sudo ufw allow 51820/udp
sudo ufw allow OpenSSH
sudo ufw --force enable
# Start WireGuard and enable on boot
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
# Verify the interface is up
sudo wg show
Expected output:
interface: wg0
public key: 4Hjk9MNXv3T2xLqP8wZRyVe0sU1oI5nB6gAcKfWmDpE=
private key: (hidden)
listening port: 51820
Part 2: Add a Client Peer
# ── ON THE SERVER — generate keys for a new peer ──────────────────────────
# (Or do this on the client and send the public key to the server)
mkdir -p ~/wireguard-clients/laptop
cd ~/wireguard-clients/laptop
wg genkey | tee private.key | wg pubkey > public.key
chmod 600 private.key
CLIENT_PRIVATE=$(cat private.key)
CLIENT_PUBLIC=$(cat public.key)
SERVER_PUBLIC=$(sudo cat /etc/wireguard/public.key)
SERVER_IP=$(curl -4s ifconfig.me) # Your server's public IP
echo "Client public key: $CLIENT_PUBLIC"
echo "Server public IP: $SERVER_IP"
# Add the peer to the server config (live, no restart needed)
sudo wg set wg0 peer "$CLIENT_PUBLIC" allowed-ips 10.10.0.2/32
# Also add to the config file so it persists after reboot
sudo tee -a /etc/wireguard/wg0.conf << EOF
[Peer]
# Laptop client
PublicKey = ${CLIENT_PUBLIC}
AllowedIPs = 10.10.0.2/32
EOF
# Save the running config
sudo wg-quick save wg0
# Verify peer was added
sudo wg show
Expected output:
interface: wg0
public key: 4Hjk9MNXv3T2xLqP8wZRyVe0sU1oI5nB6gAcKfWmDpE=
private key: (hidden)
listening port: 51820
peer: mK8vL3nPwQ2eR9tY0aB4cD6fG1hJ5iX7oU
allowed ips: 10.10.0.2/32
# Generate the client config file
tee ~/wireguard-clients/laptop/wg0.conf << EOF
[Interface]
# Client's IP on the VPN network
Address = 10.10.0.2/24
# Client's private key
PrivateKey = ${CLIENT_PRIVATE}
# DNS server (routes through VPN)
DNS = 9.9.9.9
[Peer]
# Server's public key
PublicKey = ${SERVER_PUBLIC}
# Server's public IP and port
Endpoint = ${SERVER_IP}:51820
# Route ALL traffic through VPN (full tunnel)
# For split tunnel: use 10.10.0.0/24 (only VPN traffic)
AllowedIPs = 0.0.0.0/0, ::/0
# Send keepalive every 25 seconds (prevents NAT timeout)
PersistentKeepalive = 25
EOF
chmod 600 ~/wireguard-clients/laptop/wg0.conf
cat ~/wireguard-clients/laptop/wg0.conf | grep -v PrivateKey
Expected output:
[Interface]
Address = 10.10.0.2/24
DNS = 9.9.9.9
[Peer]
PublicKey = 4Hjk9MNXv3T2xLqP8wZRyVe0sU1oI5nB6gAcKfWmDpE=
Endpoint = 5.161.88.47:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
Part 3: Connect the Client
# ── ON THE CLIENT (Linux) ─────────────────────────────────────────────────
# Copy wg0.conf to /etc/wireguard/ on the client machine
sudo cp wg0.conf /etc/wireguard/wg0.conf
sudo chmod 600 /etc/wireguard/wg0.conf
# Connect to VPN
sudo wg-quick up wg0
Expected output:
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.10.0.2/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 0.0.0.0/0 dev wg0 table 51820
[#] ip -4 rule add not fwmark 51820 table 51820
[#] ip -4 rule add table main suppress_prefixlength 0
# Verify you're connected — check handshake and traffic
sudo wg show
Expected output:
interface: wg0
public key: mK8vL3nPwQ2eR9tY0aB4cD6fG1hJ5iX7oU
private key: (hidden)
listening port: 41234
peer: 4Hjk9MNXv3T2xLqP8wZRyVe0sU1oI5nB6gAcKfWmDpE=
endpoint: 5.161.88.47:51820
allowed ips: 0.0.0.0/0, ::/0
latest handshake: 3 seconds ago ← Connected!
transfer: 1.23 KiB received, 2.48 KiB sent
# Verify your traffic exits via the VPN server's IP
curl -4s ifconfig.me
# Should return the SERVER's IP, not your local IP
Expected output:
5.161.88.47 ← Server's IP — you're exiting via the VPN
Part 4: Mobile Clients (iOS / Android)
WireGuard has official apps on iOS App Store and Google Play. The easiest setup is via QR code:
# ── ON THE SERVER — generate QR code for mobile client ────────────────────
sudo apt-get install -y qrencode
# Generate keys for mobile peer
wg genkey | tee ~/wireguard-clients/mobile/private.key | \
wg pubkey > ~/wireguard-clients/mobile/public.key
chmod 600 ~/wireguard-clients/mobile/private.key
MOBILE_PRIVATE=$(cat ~/wireguard-clients/mobile/private.key)
MOBILE_PUBLIC=$(cat ~/wireguard-clients/mobile/public.key)
# Add mobile peer to server
sudo wg set wg0 peer "$MOBILE_PUBLIC" allowed-ips 10.10.0.3/32
sudo tee -a /etc/wireguard/wg0.conf << EOF
[Peer]
# Mobile client
PublicKey = ${MOBILE_PUBLIC}
AllowedIPs = 10.10.0.3/32
EOF
sudo wg-quick save wg0
# Generate mobile client config
tee ~/wireguard-clients/mobile/wg0.conf << EOF
[Interface]
Address = 10.10.0.3/24
PrivateKey = ${MOBILE_PRIVATE}
DNS = 9.9.9.9
[Peer]
PublicKey = ${SERVER_PUBLIC}
Endpoint = ${SERVER_IP}:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF
# Display QR code — scan with the WireGuard mobile app
qrencode -t ansiutf8 < ~/wireguard-clients/mobile/wg0.conf
Expected output:
█████████████████████████
█ ▄▄▄▄▄ █▀ █▀▀▀█ ▄▄▄▄▄ █
█ █ █ █▀▀▀ ▀▀█ █ █ █
█ █▄▄▄█ █▄▀▄█▀▀█ █▄▄▄█ █
█▄▄▄▄▄▄▄█ ▀ █ ▀ ▄▄▄▄▄▄▄█
... [QR code continues] ...
Open the WireGuard app on your phone → tap + → Scan from QR Code → scan the terminal output. The tunnel configures instantly.
Part 5: Kill Switch
A kill switch blocks all internet traffic if the WireGuard tunnel drops, preventing traffic leaks over the unencrypted default route:
# ── Kill switch via UFW (client-side) ─────────────────────────────────────
# This approach blocks all outbound traffic except through wg0
# Save the server IP to allow the initial WireGuard handshake
SERVER_IP="5.161.88.47" # Replace with your server IP
# Reset UFW and configure kill switch
sudo ufw --force reset
# Allow only WireGuard handshake traffic to the server IP
sudo ufw allow out to ${SERVER_IP} port 51820 proto udp
# Allow all traffic through the WireGuard interface
sudo ufw allow out on wg0 to any
sudo ufw allow in on wg0 from any
# Allow DNS through WireGuard (prevents DNS leaks)
# Block everything else outbound
sudo ufw default deny outgoing
sudo ufw default deny incoming
sudo ufw allow OpenSSH
sudo ufw --force enable
sudo ufw status verbose
Expected output:
Status: active
Default: deny (incoming), deny (outgoing), disabled (routed)
To Action From
-- ------ ----
5.161.88.47 51820/udp ALLOW OUT Anywhere
Anywhere on wg0 ALLOW OUT Anywhere
Anywhere on wg0 ALLOW IN Anywhere
22/tcp ALLOW IN Anywhere
Test the kill switch: sudo wg-quick down wg0 → try curl ifconfig.me → should timeout (blocked). sudo wg-quick up wg0 → traffic restored.
Part 6: Split Tunnelling
Route only specific traffic through the VPN while everything else goes directly:
# ── Split tunnel: only route private/office network through VPN ───────────
# Example: 10.0.0.0/8 and 192.168.0.0/16 go through VPN, everything else direct
# Edit client's wg0.conf
sudo sed -i 's/AllowedIPs = 0.0.0.0\/0, ::\/0/AllowedIPs = 10.0.0.0\/8, 192.168.0.0\/16, 10.10.0.0\/24/' \
/etc/wireguard/wg0.conf
sudo wg-quick down wg0 && sudo wg-quick up wg0
# Verify: traffic to 10.x goes through VPN, internet traffic goes direct
ip route get 10.5.0.1 | grep wg0 # Should show wg0 interface
ip route get 8.8.8.8 | grep -v wg0 # Should show default interface
Expected output:
10.5.0.1 via 10.10.0.1 dev wg0 src 10.10.0.2 ← Through VPN
8.8.8.8 via 172.31.1.1 dev eth0 src 192.168.1.100 ← Direct
Part 7: Management and Monitoring
# ── Server management commands ────────────────────────────────────────────
# Show all peers and their connection status
sudo wg show
# Show just transfer stats
sudo wg show wg0 transfer
# Add a peer without restarting (live)
sudo wg set wg0 peer NEW_CLIENT_PUBKEY allowed-ips 10.10.0.4/32
# Remove a peer
sudo wg set wg0 peer CLIENT_PUBKEY remove
# Save running config to file (persists wg set changes across reboots)
sudo wg-quick save wg0
# Check which peers are currently active (handshake within last 3 minutes)
sudo wg show wg0 latest-handshakes | while read peer ts; do
if (( $(date +%s) - ts < 180 )); then
echo "ONLINE: $peer"
else
echo "OFFLINE: $peer (last: $(date -d @$ts '+%H:%M:%S'))"
fi
done
# Monitor bandwidth per peer
watch -n 2 'sudo wg show wg0 transfer'
Expected output:
ONLINE: mK8vL3nPwQ2eR9tY0aB4cD6fG1hJ5iX7oU ← Laptop connected
OFFLINE: rN2sT5uV8wX1yA3bC4dE0fH6iJ7kL9mO ← Mobile not connected
Sovereignty Audit
echo "=== WIREGUARD SOVEREIGNTY AUDIT ==="
echo ""
echo "[ WireGuard interface is up ]"
sudo wg show wg0 | grep -q "listening port" && \
echo " ✓ wg0 interface active ($(sudo wg show wg0 | grep port))" || \
echo " ✗ wg0 not running"
echo ""
echo "[ Traffic routes through VPN tunnel ]"
ip route show table all | grep wg0 | head -3 | sed 's/^/ /'
echo ""
echo "[ No unencrypted DNS leaks ]"
DNS_SERVER=$(resolvectl status wg0 2>/dev/null | grep "DNS Servers" | awk '{print $3}')
if [ -n "$DNS_SERVER" ]; then
echo " ✓ DNS resolver via VPN: $DNS_SERVER"
else
echo " ℹ DNS not configured per-interface — check wg0.conf DNS= directive"
fi
echo ""
echo "[ IP forwarding enabled on server ]"
sysctl net.ipv4.ip_forward | grep -q "= 1" && \
echo " ✓ IPv4 forwarding enabled" || \
echo " ✗ IPv4 forwarding disabled — add net.ipv4.ip_forward=1 to /etc/sysctl.conf"
echo ""
echo "[ No sensitive data in WireGuard config ]"
sudo grep -l "PrivateKey" /etc/wireguard/*.conf 2>/dev/null | while read f; do
perms=$(stat -c "%a" "$f")
if [ "$perms" = "600" ]; then
echo " ✓ $f — permissions $perms (owner-read only)"
else
echo " ✗ $f — permissions $perms (too permissive — run: chmod 600 $f)"
fi
done
Expected output:
=== WIREGUARD SOVEREIGNTY AUDIT ===
[ WireGuard interface is up ]
✓ wg0 interface active (listening port: 51820)
[ Traffic routes through VPN tunnel ]
10.10.0.0/24 dev wg0 proto kernel scope link
0.0.0.0/0 dev wg0 table 51820
[ No unencrypted DNS leaks ]
✓ DNS resolver via VPN: 9.9.9.9
[ IP forwarding enabled on server ]
✓ IPv4 forwarding enabled
[ No sensitive data in WireGuard config ]
✓ /etc/wireguard/wg0.conf — permissions 600 (owner-read only)
Troubleshooting
RTNETLINK answers: Operation not permitted when running wg-quick up
Cause: Not running as root, or WireGuard kernel module not loaded. Fix:
sudo modprobe wireguard # Load kernel module manually
sudo wg-quick up wg0 # Then retry as root
Handshake never completes — peer stays disconnected
Cause: Firewall blocking UDP 51820 on the server, or wrong server IP in client config. Fix:
# On server — confirm UDP 51820 is open
sudo ufw status | grep 51820
sudo ss -ulnp | grep 51820 # Should show wireguard listening
# On client — test UDP connectivity to server
nc -zuv SERVER_IP 51820 && echo "UDP open" || echo "UDP blocked"
Traffic not routing through VPN despite connected handshake
Cause: IP forwarding disabled on server, or NAT PostUp rule didn’t run. Fix:
sudo sysctl net.ipv4.ip_forward # Should be 1
sudo iptables -t nat -L POSTROUTING | grep MASQUERADE # Should show the rule
# If missing: sudo wg-quick down wg0 && sudo wg-quick up wg0
DNS leaks — DNS queries going outside VPN
Cause: System DNS resolver ignoring the WireGuard DNS= directive. Fix:
# Force DNS through systemd-resolved per interface
sudo resolvectl dns wg0 9.9.9.9
sudo resolvectl domain wg0 "~."
Conclusion
WireGuard is running: keys generated, server configured with NAT forwarding, clients added with per-peer AllowedIPs, and all traffic routing through the encrypted tunnel. The kill switch ensures traffic never falls back to the unencrypted default route if the tunnel drops. This is the complete sovereign VPN stack — no third-party VPN provider, no usage logs, no shared infrastructure.
See Linux Networking Basics 2026 for the networking fundamentals underpinning WireGuard, and Ubuntu 24.04 LTS Server Setup Checklist for the server hardening that should precede this guide.
People Also Ask
Is WireGuard safer than OpenVPN?
WireGuard’s ~4,000-line codebase is significantly smaller and more auditable than OpenVPN’s ~100,000+ lines — a smaller attack surface by definition. WireGuard uses modern cryptographic primitives (ChaCha20, Poly1305, Curve25519, BLAKE2s) with no negotiation — the cipher suite is fixed, which eliminates downgrade attacks. OpenVPN is more flexible (more cipher options, TCP support, complex certificate infrastructure) but that flexibility introduces complexity and attack surface. For a new self-hosted VPN in 2026, WireGuard is the better choice. OpenVPN’s main advantage is its longer track record in corporate environments with legacy firewall rules.
Does WireGuard log my traffic?
WireGuard itself stores no logs — it is a kernel module with no logging subsystem. Your server’s OS may log connection attempts via the kernel journal, and wg show reveals current peer connection state (endpoint IP and transfer bytes), but there is no built-in traffic log. To produce a minimal log of connection times for your own records, you can parse the wg show output with a cron job. Commercial VPN services’ “no-log” policies are legal claims about their infrastructure — WireGuard’s lack of logging is an architectural property of the code.
Can I run WireGuard on a home server behind NAT?
Yes, with caveats. If your home server is behind a residential NAT (typical ISP router), you need a way for clients to reach it. Options: (1) port forward UDP 51820 on your router to the server; (2) use a small cloud VPS as a relay — both the home server and clients connect to the VPS as peers; (3) use a DDNS service to handle dynamic IP changes. The port-forward approach works well if your ISP provides a static IP. For dynamic IPs, pair WireGuard with a DDNS service like DuckDNS (free, open-source friendly).
Further Reading
- Linux Networking Basics 2026 —
ip route, UFW, and the networking layer WireGuard builds on - Ubuntu 24.04 LTS Server Setup Checklist — harden the server before exposing it to the internet
- SSH Hardening Guide 2026 — secure the management access path alongside WireGuard
- Self-Host Gitea and Build a Sovereign CI/CD Pipeline — use WireGuard to secure access to your self-hosted services
Tested on: Ubuntu 24.04 LTS (Hetzner CX22, server) and Ubuntu 24.04 LTS (laptop client). WireGuard 1.0.20210914. Last verified: April 28, 2026.