Networking advanced ⏱ 60 minutes

WireGuard Advanced: Complex VPN Configurations

Advanced WireGuard setups. Site-to-site, multi-hop, and split tunneling configurations.

By homelabbing.io • 2025-12-17
#wireguard#vpn#advanced#site-to-site#routing

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:

  1. Cryptokey Routing - Routes are bound to public keys, not IP addresses
  2. Minimal Attack Surface - Under 4,000 lines of code
  3. Stealth by Default - Doesn’t respond to unauthenticated packets
  4. Layer 3 Protocol - Works at the network layer
  5. 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