WireGuard Advanced: Complex VPN Configurations
Advanced WireGuard setups. Site-to-site, multi-hop, and split tunneling configurations.
WireGuard is a modern, high-performance VPN protocol that combines exceptional speed with state-of-the-art cryptography. While basic WireGuard setups are straightforward, this guide explores advanced configurations for complex homelab environments, including multi-peer networks, site-to-site connectivity, dynamic routing, and high-availability setups.
Understanding WireGuard Architecture
Core Concepts
WireGuard operates on several fundamental principles:
- Cryptokey Routing - Routes are bound to public keys, not IP addresses
- Minimal Attack Surface - Under 4,000 lines of code
- Stealth by Default - Doesn’t respond to unauthenticated packets
- Layer 3 Protocol - Works at the network layer
- Stateless - No concept of sessions or handshakes after initial exchange
Network Topology Options
Point-to-Point:
[Peer A] ←--→ [Peer B]
Hub and Spoke:
[Peer B]
↑
[Peer A] ← [Hub] → [Peer C]
↓
[Peer D]
Full Mesh:
[Peer A] ←--→ [Peer B]
↕ × ↕
[Peer C] ←--→ [Peer D]
Site-to-Site:
Network A Network B
192.168.1.0/24 10.0.0.0/24
| |
[Gateway A] ←--→ [Gateway B] Advanced Installation and Setup
Docker Installation with Multi-Peer Support
Docker Compose Configuration
Create /opt/wireguard/docker-compose.yml:
version: '3.8'
services:
wireguard:
image: linuxserver/wireguard:latest
container_name: wireguard
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
- SERVERURL=wireguard.yourdomain.com
- SERVERPORT=51820
- PEERS=laptop,phone,office,branch-office
- PEERDNS=auto
- INTERNAL_SUBNET=10.13.13.0
- ALLOWEDIPS=0.0.0.0/0
- LOG_CONFS=true
volumes:
- ./config:/config
- /lib/modules:/lib/modules:ro
ports:
- 51820:51820/udp
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv4.ip_forward=1
- net.ipv6.conf.all.forwarding=1
restart: unless-stopped
networks:
- wireguard_net
networks:
wireguard_net:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16 Advanced Multi-Peer Setup
Create /opt/wireguard/config/wg0.conf:
[Interface]
Address = 10.13.13.1/24
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY_HERE
# IP forwarding and masquerading
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
PostUp = ip6tables -A FORWARD -i %i -j ACCEPT
PostUp = ip6tables -A FORWARD -o %i -j ACCEPT
PostUp = ip6tables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE
PostDown = ip6tables -D FORWARD -i %i -j ACCEPT
PostDown = ip6tables -D FORWARD -o %i -j ACCEPT
PostDown = ip6tables -t nat -D POSTROUTING -o eth+ -j MASQUERADE
# Peer 1: Mobile Device
[Peer]
PublicKey = PEER1_PUBLIC_KEY_HERE
PresharedKey = PEER1_PSK_HERE
AllowedIPs = 10.13.13.2/32
# Peer 2: Laptop
[Peer]
PublicKey = PEER2_PUBLIC_KEY_HERE
PresharedKey = PEER2_PSK_HERE
AllowedIPs = 10.13.13.3/32
# Peer 3: Branch Office Gateway
[Peer]
PublicKey = PEER3_PUBLIC_KEY_HERE
PresharedKey = PEER3_PSK_HERE
AllowedIPs = 10.13.13.4/32, 192.168.100.0/24 Native Installation (Ubuntu/Debian)
sudo apt-get update
sudo apt-get install wireguard wireguard-tools resolvconf
# Enable IP forwarding
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
echo "net.ipv6.conf.all.forwarding=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# Generate keys
umask 077
wg genkey | tee privatekey | wg pubkey > publickey
wg genpsk > presharedkey Advanced Configuration Scenarios
Multi-Site Mesh Network
Create a full mesh network where all sites can communicate directly.
Site A Configuration
# /etc/wireguard/wg0.conf - Site A (192.168.1.0/24)
[Interface]
Address = 10.13.13.1/24
PrivateKey = SITE_A_PRIVATE_KEY
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip route add 192.168.2.0/24 via 10.13.13.2
PostUp = ip route add 192.168.3.0/24 via 10.13.13.3
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = ip route del 192.168.2.0/24 via 10.13.13.2
PostDown = ip route del 192.168.3.0/24 via 10.13.13.3
# Peer: Site B
[Peer]
PublicKey = SITE_B_PUBLIC_KEY
PresharedKey = SITE_AB_PSK
Endpoint = site-b.example.com:51820
AllowedIPs = 10.13.13.2/32, 192.168.2.0/24
PersistentKeepalive = 25
# Peer: Site C
[Peer]
PublicKey = SITE_C_PUBLIC_KEY
PresharedKey = SITE_AC_PSK
Endpoint = site-c.example.com:51820
AllowedIPs = 10.13.13.3/32, 192.168.3.0/24
PersistentKeepalive = 25 Site B Configuration
# /etc/wireguard/wg0.conf - Site B (192.168.2.0/24)
[Interface]
Address = 10.13.13.2/24
PrivateKey = SITE_B_PRIVATE_KEY
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip route add 192.168.1.0/24 via 10.13.13.1
PostUp = ip route add 192.168.3.0/24 via 10.13.13.3
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = ip route del 192.168.1.0/24 via 10.13.13.1
PostDown = ip route del 192.168.3.0/24 via 10.13.13.3
# Peer: Site A
[Peer]
PublicKey = SITE_A_PUBLIC_KEY
PresharedKey = SITE_AB_PSK
Endpoint = site-a.example.com:51820
AllowedIPs = 10.13.13.1/32, 192.168.1.0/24
PersistentKeepalive = 25
# Peer: Site C
[Peer]
PublicKey = SITE_C_PUBLIC_KEY
PresharedKey = SITE_BC_PSK
Endpoint = site-c.example.com:51820
AllowedIPs = 10.13.13.3/32, 192.168.3.0/24
PersistentKeepalive = 25 Site C Configuration
# /etc/wireguard/wg0.conf - Site C (192.168.3.0/24)
[Interface]
Address = 10.13.13.3/24
PrivateKey = SITE_C_PRIVATE_KEY
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip route add 192.168.1.0/24 via 10.13.13.1
PostUp = ip route add 192.168.2.0/24 via 10.13.13.2
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = ip route del 192.168.1.0/24 via 10.13.13.1
PostDown = ip route del 192.168.2.0/24 via 10.13.13.2
# Peer: Site A
[Peer]
PublicKey = SITE_A_PUBLIC_KEY
PresharedKey = SITE_AC_PSK
Endpoint = site-a.example.com:51820
AllowedIPs = 10.13.13.1/32, 192.168.1.0/24
PersistentKeepalive = 25
# Peer: Site B
[Peer]
PublicKey = SITE_B_PUBLIC_KEY
PresharedKey = SITE_BC_PSK
Endpoint = site-b.example.com:51820
AllowedIPs = 10.13.13.2/32, 192.168.2.0/24
PersistentKeepalive = 25 Dynamic Peer Management
For environments where peers change frequently, use scripts to manage configurations.
Peer Addition Script
#!/bin/bash
# add-peer.sh
PEER_NAME=$1
PEER_IP=$2
if [ -z "$PEER_NAME" ] || [ -z "$PEER_IP" ]; then
echo "Usage: $0 PEER_NAME PEER_IP"
exit 1
fi
# Generate keys
PRIVATE_KEY=$(wg genkey)
PUBLIC_KEY=$(echo "$PRIVATE_KEY" | wg pubkey)
PSK=$(wg genpsk)
# Add to server config
cat >> /etc/wireguard/wg0.conf <<EOF
# Peer: $PEER_NAME
[Peer]
PublicKey = $PUBLIC_KEY
PresharedKey = $PSK
AllowedIPs = $PEER_IP/32
EOF
# Reload WireGuard
wg syncconf wg0 <(wg-quick strip wg0)
# Generate client config
cat > /etc/wireguard/clients/${PEER_NAME}.conf <<EOF
[Interface]
Address = $PEER_IP/24
PrivateKey = $PRIVATE_KEY
DNS = 10.13.13.1
[Peer]
PublicKey = $(cat /etc/wireguard/publickey)
PresharedKey = $PSK
Endpoint = YOUR-SERVER-IP:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF
echo "Peer $PEER_NAME added successfully"
echo "Config saved to /etc/wireguard/clients/${PEER_NAME}.conf" Peer Removal Script
#!/bin/bash
# remove-peer.sh
PEER_PUBLIC_KEY=$1
if [ -z "$PEER_PUBLIC_KEY" ]; then
echo "Usage: $0 PEER_PUBLIC_KEY"
exit 1
fi
# Remove from interface
wg set wg0 peer "$PEER_PUBLIC_KEY" remove
echo "Peer removed successfully" High-Availability Configuration
Implement redundant WireGuard servers for failover.
Primary Server Configuration
# /etc/wireguard/wg0.conf - Primary Server
[Interface]
Address = 10.13.13.1/24
PrivateKey = PRIMARY_PRIVATE_KEY
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Enable VRRP for failover
PostUp = keepalived -f /etc/keepalived/keepalived.conf
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = killall keepalived
[Peer]
# Client peers... Keepalived Configuration
# /etc/keepalived/keepalived.conf
vrrp_script check_wireguard {
script "/usr/local/bin/check_wg.sh"
interval 5
weight 10
}
vrrp_instance WG_VIP {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass your_secure_password
}
virtual_ipaddress {
192.168.1.100/24
}
track_script {
check_wireguard
}
} Health Check Script
#!/bin/bash
# /usr/local/bin/check_wg.sh
if ! ip link show wg0 > /dev/null 2>&1; then
exit 1
fi
if ! wg show wg0 > /dev/null 2>&1; then
exit 1
fi
exit 0 Client Configuration with Failover
[Interface]
Address = 10.13.13.100/24
PrivateKey = CLIENT_PRIVATE_KEY
DNS = 10.13.13.1
# Primary server
[Peer]
PublicKey = PRIMARY_PUBLIC_KEY
PresharedKey = PRIMARY_PSK
Endpoint = primary.vpn.example.com:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
# Backup server (will be used if primary fails)
[Peer]
PublicKey = BACKUP_PUBLIC_KEY
PresharedKey = BACKUP_PSK
Endpoint = backup.vpn.example.com:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25 Split Tunneling with Policy-Based Routing
Route only specific traffic through VPN while sending other traffic directly.
Server Configuration
[Interface]
Address = 10.13.13.1/24
PrivateKey = SERVER_PRIVATE_KEY
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = CLIENT_PUBLIC_KEY
AllowedIPs = 10.13.13.2/32 Client Configuration with Split Tunnel
[Interface]
Address = 10.13.13.2/24
PrivateKey = CLIENT_PRIVATE_KEY
DNS = 10.13.13.1
# Route tables
PostUp = ip rule add from 10.13.13.2 table 200
PostUp = ip route add default via 10.13.13.1 table 200
PostUp = ip route add 192.168.1.0/24 via 10.13.13.1 table 200
PostUp = ip route add 10.0.0.0/8 via 10.13.13.1 table 200
PostDown = ip rule del from 10.13.13.2 table 200
PostDown = ip route del default via 10.13.13.1 table 200
PostDown = ip route del 192.168.1.0/24 via 10.13.13.1 table 200
PostDown = ip route del 10.0.0.0/8 via 10.13.13.1 table 200
[Peer]
PublicKey = SERVER_PUBLIC_KEY
PresharedKey = PSK_HERE
Endpoint = vpn.example.com:51820
AllowedIPs = 192.168.1.0/24, 10.0.0.0/8
PersistentKeepalive = 25 IPv6 Dual-Stack Configuration
[Interface]
Address = 10.13.13.1/24, fd00:13:13::1/64
PrivateKey = SERVER_PRIVATE_KEY
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip6tables -A FORWARD -i %i -j ACCEPT
PostUp = ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = ip6tables -D FORWARD -i %i -j ACCEPT
PostDown = ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = CLIENT_PUBLIC_KEY
AllowedIPs = 10.13.13.2/32, fd00:13:13::2/128 Advanced Security Configurations
Pre-Shared Keys (PSK)
Always use PSKs for post-quantum security:
# Generate PSK
wg genpsk
# Add to both server and client configs
[Peer]
PublicKey = PEER_PUBLIC_KEY
PresharedKey = GENERATED_PSK_HERE
AllowedIPs = 10.13.13.2/32 Firewall Rules for Fine-Grained Control
nftables Configuration
# /etc/nftables.conf
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Allow established connections
ct state established,related accept
# Allow loopback
iif lo accept
# Allow WireGuard
udp dport 51820 accept
# Allow from WireGuard interface
iifname wg0 accept
}
chain forward {
type filter hook forward priority 0; policy drop;
# Allow established connections
ct state established,related accept
# Allow WireGuard to LAN
iifname wg0 oifname eth0 accept
# Allow LAN to WireGuard
iifname eth0 oifname wg0 ct state established,related accept
# Allow specific services from VPN
iifname wg0 tcp dport { 22, 80, 443 } accept
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table inet nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
# Masquerade WireGuard traffic
oifname eth0 ip saddr 10.13.13.0/24 masquerade
}
} Apply configuration:
sudo nft -f /etc/nftables.conf
sudo systemctl enable nftables Port Knocking for Extra Security
Hide WireGuard port behind port knocking:
# /etc/knockd.conf
[options]
logfile = /var/log/knockd.log
[openWG]
sequence = 7000,8000,9000
seq_timeout = 15
command = /usr/sbin/iptables -I INPUT -s %IP% -p udp --dport 51820 -j ACCEPT
tcpflags = syn
[closeWG]
sequence = 9000,8000,7000
seq_timeout = 15
command = /usr/sbin/iptables -D INPUT -s %IP% -p udp --dport 51820 -j ACCEPT
tcpflags = syn Client side:
# Open WireGuard port
knock vpn.example.com 7000 8000 9000
# Connect
wg-quick up wg0
# Close port when done
knock vpn.example.com 9000 8000 7000 Certificate-Based Authentication
Use certificates for additional authentication layer:
#!/bin/bash
# verify-peer.sh
PEER_PUBLIC_KEY=$1
PEER_CERT="/etc/wireguard/certs/${PEER_PUBLIC_KEY}.cert"
if [ ! -f "$PEER_CERT" ]; then
echo "No certificate found for peer"
exit 1
fi
# Verify certificate
openssl verify -CAfile /etc/wireguard/ca.crt "$PEER_CERT"
if [ $? -eq 0 ]; then
echo "Certificate valid"
exit 0
else
echo "Certificate invalid"
exit 1
fi Performance Optimization
MTU Tuning
Determine optimal MTU:
# Test MTU sizes
ping -M do -s 1472 vpn.example.com
# If successful, MTU is 1500 (1472 + 28 byte overhead)
# If failed, reduce by 10 until successful Set optimal MTU:
[Interface]
Address = 10.13.13.1/24
MTU = 1420
PrivateKey = PRIVATE_KEY CPU Affinity and Threading
# Check WireGuard CPU usage
top -p $(pидof wg-crypt-wg0)
# Set CPU affinity
taskset -cp 0,1 $(pидof wg-crypt-wg0) Offload and Hardware Acceleration
Enable hardware offload where available:
# Check offload capabilities
ethtool -k eth0 | grep offload
# Enable offloads
ethtool -K eth0 tx-checksum-ipv4 on
ethtool -K eth0 tx-checksum-ipv6 on
ethtool -K eth0 sg on
ethtool -K eth0 tso on
ethtool -K eth0 gso on Connection Optimization
[Interface]
Address = 10.13.13.1/24
MTU = 1420
PrivateKey = PRIVATE_KEY
# Optimize for throughput
PostUp = ethtool -G wg0 rx 4096 tx 4096 2>/dev/null || true
PostUp = ethtool -K wg0 gso on 2>/dev/null || true
[Peer]
PublicKey = PEER_PUBLIC_KEY
AllowedIPs = 10.13.13.2/32
PersistentKeepalive = 25 Dynamic DNS Integration
For dynamic IP addresses:
#!/bin/bash
# /usr/local/bin/update-wg-endpoint.sh
PEER_NAME="branch-office"
DYNAMIC_HOSTNAME="branch.ddns.net"
# Resolve current IP
NEW_IP=$(dig +short "$DYNAMIC_HOSTNAME" | tail -n1)
if [ -z "$NEW_IP" ]; then
echo "Failed to resolve $DYNAMIC_HOSTNAME"
exit 1
fi
# Update WireGuard peer endpoint
wg set wg0 peer "PEER_PUBLIC_KEY" endpoint "${NEW_IP}:51820"
echo "Updated $PEER_NAME endpoint to $NEW_IP" Automate with cron:
# Update endpoints every 5 minutes
*/5 * * * * /usr/local/bin/update-wg-endpoint.sh Monitoring and Observability
Prometheus Exporter
#!/usr/bin/env python3
# wireguard-exporter.py
from prometheus_client import start_http_server, Gauge, Counter
import subprocess
import re
import time
peer_rx_bytes = Counter('wireguard_peer_rx_bytes_total',
'Bytes received from peer', ['public_key', 'name'])
peer_tx_bytes = Counter('wireguard_peer_tx_bytes_total',
'Bytes transmitted to peer', ['public_key', 'name'])
peer_latest_handshake = Gauge('wireguard_peer_latest_handshake_seconds',
'Last handshake time', ['public_key', 'name'])
peer_connected = Gauge('wireguard_peer_connected',
'Peer connection status', ['public_key', 'name'])
PEER_NAMES = {
'PUBLIC_KEY_1': 'laptop',
'PUBLIC_KEY_2': 'phone',
'PUBLIC_KEY_3': 'office'
}
def collect_metrics():
output = subprocess.check_output(['wg', 'show', 'wg0', 'dump']).decode()
for line in output.strip().split('\n')[1:]: # Skip header
parts = line.split('\t')
public_key = parts[0]
rx_bytes = int(parts[5])
tx_bytes = int(parts[6])
latest_handshake = int(parts[4])
peer_name = PEER_NAMES.get(public_key, 'unknown')
peer_rx_bytes.labels(public_key=public_key, name=peer_name)._value.set(rx_bytes)
peer_tx_bytes.labels(public_key=public_key, name=peer_name)._value.set(tx_bytes)
peer_latest_handshake.labels(public_key=public_key, name=peer_name).set(latest_handshake)
# Consider connected if handshake within last 3 minutes
connected = 1 if (time.time() - latest_handshake) < 180 else 0
peer_connected.labels(public_key=public_key, name=peer_name).set(connected)
if __name__ == '__main__':
start_http_server(9586)
print("WireGuard exporter started on port 9586")
while True:
try:
collect_metrics()
except Exception as e:
print(f"Error collecting metrics: {e}")
time.sleep(15) Grafana Dashboard JSON
{
"dashboard": {
"title": "WireGuard Monitoring",
"panels": [
{
"title": "Connected Peers",
"targets": [
{
"expr": "wireguard_peer_connected"
}
],
"type": "stat"
},
{
"title": "Bandwidth Usage",
"targets": [
{
"expr": "rate(wireguard_peer_rx_bytes_total[5m])",
"legendFormat": "RX - {{name}}"
},
{
"expr": "rate(wireguard_peer_tx_bytes_total[5m])",
"legendFormat": "TX - {{name}}"
}
],
"type": "graph"
},
{
"title": "Last Handshake",
"targets": [
{
"expr": "time() - wireguard_peer_latest_handshake_seconds"
}
],
"type": "graph"
}
]
}
} Log Analysis
# Monitor WireGuard logs
journalctl -u wg-quick@wg0 -f
# Extract connection events
journalctl -u wg-quick@wg0 | grep "Handshake"
# Analyze peer activity
wg show wg0 latest-handshakes Health Check Script
#!/bin/bash
# /usr/local/bin/wg-health-check.sh
INTERFACE="wg0"
ALERT_EMAIL="[email protected]"
# Check interface status
if ! ip link show "$INTERFACE" > /dev/null 2>&1; then
echo "WireGuard interface $INTERFACE is down!" | mail -s "WG Alert" "$ALERT_EMAIL"
systemctl restart wg-quick@wg0
exit 1
fi
# Check peer handshakes
while read -r public_key latest_handshake; do
age=$(($(date +%s) - latest_handshake))
if [ $age -gt 300 ]; then
peer_name=$(grep "$public_key" /etc/wireguard/wg0.conf -B2 | grep "# Peer:" | cut -d: -f2 | xargs)
echo "Peer $peer_name hasn't connected in $age seconds" | mail -s "WG Peer Alert" "$ALERT_EMAIL"
fi
done < <(wg show "$INTERFACE" latest-handshakes)
# Check traffic
rx_bytes=$(wg show "$INTERFACE" transfer | awk '{sum+=$2} END {print sum}')
tx_bytes=$(wg show "$INTERFACE" transfer | awk '{sum+=$3} END {print sum}')
echo "Interface: $INTERFACE"
echo "RX: $((rx_bytes / 1024 / 1024)) MB"
echo "TX: $((tx_bytes / 1024 / 1024)) MB" Troubleshooting
Connectivity Issues
Check Interface Status
# Verify interface is up
ip link show wg0
# Check addresses
ip addr show wg0
# Verify routes
ip route show dev wg0 Test Handshake
# Show all peers
wg show wg0
# Check specific peer
wg show wg0 peer PEER_PUBLIC_KEY
# Monitor handshakes
watch -n 1 wg show wg0 latest-handshakes Debug with tcpdump
# Capture WireGuard traffic
tcpdump -i any -n port 51820
# Monitor WireGuard interface
tcpdump -i wg0 -n Routing Problems
Verify IP Forwarding
# Check IPv4 forwarding
sysctl net.ipv4.ip_forward
# Check IPv6 forwarding
sysctl net.ipv6.conf.all.forwarding
# Enable if disabled
sudo sysctl -w net.ipv4.ip_forward=1
sudo sysctl -w net.ipv6.conf.all.forwarding=1 Check iptables Rules
# View NAT rules
iptables -t nat -L -v -n
# View filter rules
iptables -L FORWARD -v -n
# Check packet counters
iptables -L -v -n | grep wg0 Trace Routing
# From VPN client
traceroute -n 192.168.1.1
# Check reverse path
# On server
tcpdump -i wg0 icmp Performance Issues
Check MTU
# Current MTU
ip link show wg0
# Test with different sizes
ping -M do -s 1400 PEER_IP
ping -M do -s 1300 PEER_IP Monitor CPU Usage
# WireGuard processes
top -p $(pgrep wg)
# System-wide
vmstat 1
# Per-interface stats
ip -s link show wg0 Measure Throughput
# Using iperf3
# On server
iperf3 -s
# On client
iperf3 -c SERVER_IP Key Management Issues
Verify Keys
# Check private key format
wg show wg0 private-key
# Verify public key matches
echo "PRIVATE_KEY" | wg pubkey
# Check peer public keys
wg show wg0 peers Regenerate Keys
# Generate new keys
umask 077
wg genkey | tee new-privatekey | wg pubkey > new-publickey
# Update configuration
wg set wg0 private-key new-privatekey
# Update peers with new public key Integration Examples
Integration with FRRouting (BGP/OSPF)
# Install FRRouting
sudo apt-get install frr
# Enable OSPF
sudo sed -i 's/ospfd=no/ospfd=yes/' /etc/frr/daemons
# Configure OSPF
cat > /etc/frr/ospfd.conf <<EOF
router ospf
network 10.13.13.0/24 area 0
network 192.168.1.0/24 area 0
passive-interface default
no passive-interface wg0
EOF
sudo systemctl restart frr Integration with DNS (Pi-hole/AdGuard)
# Configure WireGuard to use Pi-hole
# In client config:
[Interface]
Address = 10.13.13.2/24
DNS = 10.13.13.1
PrivateKey = CLIENT_PRIVATE_KEY
[Peer]
# ... peer configuration Integration with Reverse Proxy
# /etc/nginx/sites-available/wireguard-services
upstream homelab_service {
server 192.168.1.10:8080;
}
server {
listen 10.13.13.1:80;
server_name services.wg.internal;
location / {
proxy_pass http://homelab_service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Only allow VPN clients
allow 10.13.13.0/24;
deny all;
}
} Backup and Disaster Recovery
Configuration Backup
#!/bin/bash
# backup-wireguard.sh
BACKUP_DIR="/backup/wireguard"
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"
# Backup configs
tar -czf "$BACKUP_DIR/wg-config-$DATE.tar.gz" /etc/wireguard/
# Backup keys separately (encrypted)
tar -czf - /etc/wireguard/*.conf /etc/wireguard/privatekey | openssl enc -aes-256-cbc -salt -out "$BACKUP_DIR/wg-secure-$DATE.tar.gz.enc"
# Retain only last 7 backups
find "$BACKUP_DIR" -name "wg-*.tar.gz*" -mtime +7 -delete
echo "Backup completed: $BACKUP_DIR/wg-config-$DATE.tar.gz" Configuration Restore
#!/bin/bash
# restore-wireguard.sh
BACKUP_FILE=$1
if [ -z "$BACKUP_FILE" ]; then
echo "Usage: $0 BACKUP_FILE"
exit 1
fi
# Stop WireGuard
wg-quick down wg0
# Restore configuration
tar -xzf "$BACKUP_FILE" -C /
# Start WireGuard
wg-quick up wg0
echo "Configuration restored from $BACKUP_FILE" Conclusion
Advanced WireGuard configurations enable complex networking scenarios while maintaining security and performance. Key takeaways:
- Use mesh topologies for redundancy and reduced latency
- Implement PSKs for post-quantum security
- Monitor peer connections and handshakes
- Optimize MTU and routing for performance
- Automate peer management for dynamic environments
- Implement proper backup and disaster recovery
WireGuard’s simplicity and flexibility make it ideal for advanced homelab networking, from simple point-to-point connections to complex multi-site mesh networks with dynamic routing and high availability.
Additional Resources
- Official Documentation: https://www.wireguard.com/
- Protocol Specification: https://www.wireguard.com/papers/wireguard.pdf
- Awesome WireGuard: https://github.com/cedrickchee/awesome-wireguard
- WireGuard Tools: https://git.zx2c4.com/wireguard-tools/
- Performance Benchmarks: https://www.wireguard.com/performance/