Most WireGuard guides only cover IPv4. This leaves an IPv6 leak problem: if your client has native IPv6 connectivity but your VPN tunnel only handles IPv4, traffic to IPv6 addresses bypasses the tunnel entirely and exposes your real IP. This guide sets up WireGuard with both IPv4 and IPv6 routing to prevent leaks.
Prerequisites
- A VPS with a public IPv4 address
- IPv6 support on the VPS (most cloud providers offer this — check your VPS dashboard)
- WireGuard installed on both server and client
- Ubuntu 22.04 on the server
Step 1: Enable IPv6 on the VPS
Check if your VPS has an IPv6 address:
ip -6 addr show
# Look for a global address (not just link-local fe80::)
Enable IPv6 forwarding:
sudo tee -a /etc/sysctl.conf > /dev/null <<'EOF'
# WireGuard IPv4 forwarding
net.ipv4.ip_forward = 1
# WireGuard IPv6 forwarding
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.default.forwarding = 1
EOF
sudo sysctl -p
Step 2: Choose Addressing Scheme
For the WireGuard tunnel network:
- IPv4:
10.100.0.0/24(server at10.100.0.1, clients at10.100.0.2+) - IPv6:
fd42:dead:beef::/64(ULA — Unique Local Address, globally unique but not publicly routed) - Server:
fd42:dead:beef::1 - Clients:
fd42:dead:beef::2,fd42:dead:beef::3, etc.
The ULA range (fd00::/8) is analogous to RFC1918 for IPv6. Use it for WireGuard tunnel addresses.
Step 3: Generate Keys
# Install WireGuard
sudo apt install wireguard
# Server keys
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
chmod 600 /etc/wireguard/server_private.key
# Client keys (run on client or generate on server and distribute securely)
wg genkey | tee /etc/wireguard/client1_private.key | wg pubkey > /etc/wireguard/client1_public.key
cat /etc/wireguard/server_public.key
cat /etc/wireguard/client1_public.key
Step 4: Server Configuration
Create /etc/wireguard/wg0.conf on the server:
[Interface]
# Server Nebula IPv4 and IPv6 addresses
Address = 10.100.0.1/24, fd42:dead:beef::1/64
# Listen port
ListenPort = 51820
# Server private key
PrivateKey = SERVER_PRIVATE_KEY_HERE
# NAT rules for IPv4 (replace eth0 with your public interface name)
PostUp = iptables -A FORWARD -i %i -j ACCEPT; \
iptables -A FORWARD -o %i -j ACCEPT; \
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# IPv6 NAT (using ip6tables)
# If your VPS has a single global IPv6, use NAT
PostUp = ip6tables -A FORWARD -i %i -j ACCEPT; \
ip6tables -A FORWARD -o %i -j ACCEPT; \
ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; \
iptables -D FORWARD -o %i -j ACCEPT; \
iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = ip6tables -D FORWARD -i %i -j ACCEPT; \
ip6tables -D FORWARD -o %i -j ACCEPT; \
ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# Client 1
PublicKey = CLIENT1_PUBLIC_KEY_HERE
AllowedIPs = 10.100.0.2/32, fd42:dead:beef::2/128
Note: If your VPS has a full /64 IPv6 prefix (not just a single address), you can route client IPv6 addresses directly without NAT. Replace the ip6tables MASQUERADE rules with:
PostUp = ip6tables -A FORWARD -i %i -j ACCEPT; ip6tables -A FORWARD -o %i -j ACCEPT
And configure each client with a unique address from your delegated prefix.
Step 5: Enable IPv6 NAT Support in the Kernel
IPv6 NAT (MASQUERADE) requires a kernel module:
sudo modprobe ip6table_nat
sudo modprobe ip6_tables
# Make it persistent
echo "ip6table_nat" | sudo tee -a /etc/modules-load.d/ip6tables.conf
echo "ip6_tables" | sudo tee -a /etc/modules-load.d/ip6tables.conf
Start WireGuard:
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
Step 6: Client Configuration
Create /etc/wireguard/wg0.conf on the client (Linux) or use the WireGuard app (iOS/Android/Windows):
[Interface]
# Client's tunnel addresses
Address = 10.100.0.2/32, fd42:dead:beef::2/128
# Client private key
PrivateKey = CLIENT1_PRIVATE_KEY_HERE
# Route ALL traffic (both IPv4 and IPv6) through the tunnel
DNS = 10.100.0.1, fd42:dead:beef::1
[Peer]
PublicKey = SERVER_PUBLIC_KEY_HERE
Endpoint = YOUR_VPS_IP:51820
# Route all IPv4 and IPv6 through tunnel
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
The key setting is AllowedIPs = 0.0.0.0/0, ::/0 — this routes all IPv4 AND IPv6 traffic through the tunnel, preventing IPv6 leaks.
Bring the interface up:
sudo wg-quick up wg0
Step 7: Run a DNS Server for IPv6 (Optional)
If you want to serve DNS over IPv6 from your WireGuard server, use Unbound:
sudo apt install unbound
sudo tee /etc/unbound/unbound.conf.d/wireguard.conf > /dev/null <<'EOF'
server:
# Listen on WireGuard interface
interface: 10.100.0.1
interface: fd42:dead:beef::1
# Allow queries from tunnel
access-control: 10.100.0.0/24 allow
access-control: fd42:dead:beef::/64 allow
# Block local queries except from tunnel
access-control: 0.0.0.0/0 refuse
access-control: ::/0 refuse
# Enable IPv6
do-ip6: yes
prefer-ip6: no
# DNSSEC
auto-trust-anchor-file: "/var/lib/unbound/root.key"
EOF
sudo systemctl enable --now unbound
Step 8: Verify No IPv6 Leaks
# On the client, check your public IPs
curl -4 https://ifconfig.me # Should show your VPS IPv4
curl -6 https://ifconfig.me # Should show your VPS IPv6 (not your ISP's IPv6)
# Check all routes
ip -6 route show
# Should show: default dev wg0 (or via the tunnel)
# DNS leak test
drill @fd42:dead:beef::1 google.com
# Should resolve without hitting your ISP's DNS
If curl -6 https://ifconfig.me returns your home IPv6, the tunnel is not capturing IPv6 traffic. Check:
- That
AllowedIPsincludes::/0 - That ip6tables NAT is loaded on the server
- That your client OS is not routing IPv6 outside the tunnel
Related Reading
- How to Set Up WireGuard on VPS for Personal VPN
- How to Configure UFW Firewall on Ubuntu
- How to Set Up Nebula Mesh VPN for Teams
- Set Up a Personal VPN with WireGuard
- AI Coding Assistant Session Data Lifecycle
- How to Audit What Source Code AI Coding Tools Transmit