Implement WireGuard multi-site mesh networking with automatic routing and failover

Advanced 45 min Apr 26, 2026 20 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Deploy a scalable WireGuard mesh network across multiple sites with automatic routing, failover mechanisms, and centralized management for high-availability site-to-site connectivity.

Prerequisites

  • Multiple servers with public IP addresses
  • Root access on all mesh nodes
  • Basic understanding of networking and routing
  • Firewall configuration knowledge

What this solves

Traditional site-to-site VPN setups create single points of failure with hub-and-spoke architectures. This tutorial implements a WireGuard mesh network where multiple sites can communicate directly with automatic routing and failover. You get encrypted inter-site connectivity that automatically routes around failed nodes, perfect for distributed infrastructure, branch offices, or multi-datacenter deployments.

Step-by-step installation

Install WireGuard on all nodes

Install WireGuard on each server that will participate in the mesh network. These will be your site gateway nodes.

sudo apt update
sudo apt install -y wireguard iptables-persistent bird2
sudo dnf update -y
sudo dnf install -y wireguard-tools iptables-services bird

Enable IP forwarding system-wide

Configure each node to forward packets between network interfaces for mesh routing.

net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.core.netdev_max_backlog = 2000
net.ipv4.tcp_congestion_control = bbr
sudo sysctl --system

Generate key pairs for each site

Create unique cryptographic keys for each mesh node. Store these securely as they authenticate your sites.

sudo mkdir -p /etc/wireguard/keys
sudo chmod 700 /etc/wireguard/keys
sudo wg genkey | sudo tee /etc/wireguard/keys/private.key | wg pubkey | sudo tee /etc/wireguard/keys/public.key
sudo chmod 600 /etc/wireguard/keys/private.key
sudo chmod 644 /etc/wireguard/keys/public.key

Plan your mesh topology and IP allocation

Design your network addressing scheme. Use a dedicated subnet for the mesh overlay and plan your site networks.

# Mesh Network Plan

Overlay Network: 10.100.0.0/16

Site A (London): Gateway 10.100.1.1/24, LAN 192.168.1.0/24

Site B (Berlin): Gateway 10.100.2.1/24, LAN 192.168.2.0/24

Site C (Paris): Gateway 10.100.3.1/24, LAN 192.168.3.0/24

Site D (Madrid): Gateway 10.100.4.1/24, LAN 192.168.4.0/24

Configure WireGuard mesh interface for Site A

Create the WireGuard configuration for the first site. This node will connect to all other sites directly.

[Interface]
PrivateKey = $(sudo cat /etc/wireguard/keys/private.key)
Address = 10.100.1.1/24
ListenPort = 51820
PostUp = iptables -t nat -A POSTROUTING -s 10.100.0.0/16 -o eth0 -j MASQUERADE
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -A FORWARD -o wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -s 10.100.0.0/16 -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -o wg0 -j ACCEPT

Site B (Berlin)

[Peer] PublicKey = SITE_B_PUBLIC_KEY_HERE Endpoint = 203.0.113.20:51820 AllowedIPs = 10.100.2.0/24, 192.168.2.0/24 PersistentKeepalive = 25

Site C (Paris)

[Peer] PublicKey = SITE_C_PUBLIC_KEY_HERE Endpoint = 203.0.113.30:51820 AllowedIPs = 10.100.3.0/24, 192.168.3.0/24 PersistentKeepalive = 25

Site D (Madrid)

[Peer] PublicKey = SITE_D_PUBLIC_KEY_HERE Endpoint = 203.0.113.40:51820 AllowedIPs = 10.100.4.0/24, 192.168.4.0/24 PersistentKeepalive = 25

Configure dynamic routing with BIRD

Set up BIRD routing daemon for automatic route discovery and failover between mesh nodes.

log syslog all;
router id 10.100.1.1;

protocol kernel {
    learn;
    persist;
    scan time 20;
    import all;
    export all;
}

protocol device {
    scan time 10;
}

protocol direct {
    interface "wg0", "eth0";
}

protocol ospf v2 mesh {
    tick 2;
    rfc1583compat yes;
    
    area 0.0.0.0 {
        interface "wg0" {
            cost 10;
            hello 5;
            dead 20;
            type pointopoint;
        };
        
        interface "eth0" {
            cost 100;
            hello 10;
            dead 40;
            stub yes;
        };
    };
}

Create mesh management scripts

Build automation scripts for mesh operations, health monitoring, and failover detection.

#!/bin/bash

WireGuard Mesh Health Monitor

MESH_CONFIG="/etc/wireguard/wg0.conf" LOG_FILE="/var/log/wireguard-mesh.log" EMAIL_ALERT="admin@example.com" log_message() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE" } check_peer_connectivity() { local peer_ip="$1" local peer_name="$2" if ping -c 3 -W 2 "$peer_ip" >/dev/null 2>&1; then log_message "HEALTHY: $peer_name ($peer_ip) is reachable" return 0 else log_message "ALERT: $peer_name ($peer_ip) is unreachable" return 1 fi } check_routing_table() { local expected_routes=$(grep -c "AllowedIPs" "$MESH_CONFIG") local active_routes=$(ip route | grep -c "dev wg0") if [ "$active_routes" -ge "$expected_routes" ]; then log_message "ROUTING: $active_routes routes active (expected >= $expected_routes)" else log_message "ALERT: Only $active_routes routes active (expected >= $expected_routes)" systemctl restart bird fi }

Monitor peer connectivity

check_peer_connectivity "10.100.2.1" "Berlin" check_peer_connectivity "10.100.3.1" "Paris" check_peer_connectivity "10.100.4.1" "Madrid"

Verify routing

check_routing_table

Check WireGuard interface status

if ! wg show wg0 >/dev/null 2>&1; then log_message "ALERT: WireGuard interface wg0 is down" systemctl restart wg-quick@wg0 fi
sudo chmod 755 /usr/local/bin/wg-mesh-monitor.sh

Configure automatic failover script

Create a failover mechanism that reroutes traffic when primary paths fail.

#!/bin/bash

WireGuard Mesh Failover Handler

BIRD_SOCKET="/var/run/bird/bird.ctl" LOG_FILE="/var/log/wireguard-mesh.log" log_message() { echo "$(date '+%Y-%m-%d %H:%M:%S') FAILOVER: $1" >> "$LOG_FILE" } trigger_route_update() { local failed_peer="$1" log_message "Updating routes due to $failed_peer failure" # Recalculate OSPF routes echo "configure\nprotocol ospf mesh { disabled; }\nprotocol ospf mesh { disabled no; }\nexit" | birdc # Wait for convergence sleep 10 # Verify new routes ip route show table main | grep "dev wg0" | while read route; do log_message "Active route: $route" done } handle_peer_failure() { local peer_ip="$1" local peer_name="$2" # Check if peer is consistently down local failures=0 for i in {1..3}; do if ! ping -c 1 -W 2 "$peer_ip" >/dev/null 2>&1; then ((failures++)) fi sleep 2 done if [ "$failures" -eq 3 ]; then log_message "Peer $peer_name ($peer_ip) confirmed down, initiating failover" trigger_route_update "$peer_name" # Send alert echo "WireGuard mesh peer $peer_name failed. Automatic failover activated." | mail -s "Mesh Network Alert: $peer_name Down" admin@example.com fi }

Check each peer and trigger failover if needed

handle_peer_failure "10.100.2.1" "Berlin" handle_peer_failure "10.100.3.1" "Paris" handle_peer_failure "10.100.4.1" "Madrid"
sudo chmod 755 /usr/local/bin/wg-mesh-failover.sh

Set up automated monitoring with systemd timers

Configure regular health checks and automatic failover detection using systemd timers.

[Unit]
Description=WireGuard Mesh Network Monitor
After=wg-quick@wg0.service bird.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/wg-mesh-monitor.sh
User=root
StandardOutput=journal
StandardError=journal
[Unit]
Description=Run WireGuard Mesh Monitor every 2 minutes
Requires=wg-mesh-monitor.service

[Timer]
OnCalendar=::00/120
Persistent=true

[Install]
WantedBy=timers.target
[Unit]
Description=WireGuard Mesh Failover Handler
After=wg-quick@wg0.service bird.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/wg-mesh-failover.sh
User=root
StandardOutput=journal
StandardError=journal
[Unit]
Description=Run WireGuard Mesh Failover Check every 5 minutes
Requires=wg-mesh-failover.service

[Timer]
OnCalendar=::00/300
Persistent=true

[Install]
WantedBy=timers.target

Configure firewall rules for mesh traffic

Open the necessary ports and configure packet filtering for secure mesh communication.

# Allow WireGuard traffic
sudo iptables -I INPUT -p udp --dport 51820 -j ACCEPT

Allow mesh subnet traffic

sudo iptables -I INPUT -s 10.100.0.0/16 -j ACCEPT sudo iptables -I FORWARD -s 10.100.0.0/16 -j ACCEPT sudo iptables -I FORWARD -d 10.100.0.0/16 -j ACCEPT

Allow OSPF protocol

sudo iptables -I INPUT -p ospf -j ACCEPT sudo iptables -I OUTPUT -p ospf -j ACCEPT

Save rules

sudo netfilter-persistent save
sudo service iptables save

Start and enable all services

Activate the WireGuard interface, routing daemon, and monitoring services.

# Enable WireGuard interface
sudo systemctl enable --now wg-quick@wg0

Enable BIRD routing daemon

sudo systemctl enable --now bird

Enable monitoring timers

sudo systemctl enable --now wg-mesh-monitor.timer sudo systemctl enable --now wg-mesh-failover.timer

Check service status

sudo systemctl status wg-quick@wg0 bird wg-mesh-monitor.timer

Replicate configuration for remaining sites

Deploy similar configurations to Sites B, C, and D with appropriate IP addresses and peer configurations.

[Interface]
PrivateKey = SITE_B_PRIVATE_KEY
Address = 10.100.2.1/24
ListenPort = 51820
PostUp = iptables -t nat -A POSTROUTING -s 10.100.0.0/16 -o eth0 -j MASQUERADE
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -A FORWARD -o wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -s 10.100.0.0/16 -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -o wg0 -j ACCEPT

Site A (London)

[Peer] PublicKey = SITE_A_PUBLIC_KEY Endpoint = 203.0.113.10:51820 AllowedIPs = 10.100.1.0/24, 192.168.1.0/24 PersistentKeepalive = 25

Site C (Paris)

[Peer] PublicKey = SITE_C_PUBLIC_KEY Endpoint = 203.0.113.30:51820 AllowedIPs = 10.100.3.0/24, 192.168.3.0/24 PersistentKeepalive = 25

Site D (Madrid)

[Peer] PublicKey = SITE_D_PUBLIC_KEY Endpoint = 203.0.113.40:51820 AllowedIPs = 10.100.4.0/24, 192.168.4.0/24 PersistentKeepalive = 25
Note: Update the router ID in /etc/bird/bird.conf to match each site's gateway IP (10.100.2.1 for Site B, 10.100.3.1 for Site C, etc.)

Verify your setup

Test mesh connectivity and verify automatic routing between all sites.

# Check WireGuard interface status
sudo wg show wg0

Verify all peers are connected

sudo wg show wg0 allowed-ips

Test connectivity to all mesh nodes

ping -c 3 10.100.2.1 # Site B ping -c 3 10.100.3.1 # Site C ping -c 3 10.100.4.1 # Site D

Check OSPF routing table

sudo birdc show route

Verify site-to-site connectivity

ping -c 3 192.168.2.10 # Test host at Site B ping -c 3 192.168.3.10 # Test host at Site C

Check monitoring logs

sudo journalctl -u wg-mesh-monitor.service --since "1 hour ago" tail -f /var/log/wireguard-mesh.log

Test failover (disable one peer temporarily)

sudo wg-quick down wg0 sudo wg-quick up wg0

Site-to-site connectivity testing

Perform comprehensive testing to verify mesh functionality and failover behavior.

#!/bin/bash

Comprehensive mesh connectivity test

SITES=("10.100.1.1:London" "10.100.2.1:Berlin" "10.100.3.1:Paris" "10.100.4.1:Madrid") LAN_SUBNETS=("192.168.1.0/24" "192.168.2.0/24" "192.168.3.0/24" "192.168.4.0/24") echo "=== WireGuard Mesh Connectivity Test ===" echo "Started: $(date)" echo

Test mesh node connectivity

echo "Testing mesh node connectivity:" for site in "${SITES[@]}"; do ip="${site%%:*}" name="${site##*:}" if ping -c 3 -W 2 "$ip" >/dev/null 2>&1; then echo "✓ $name ($ip) - REACHABLE" else echo "✗ $name ($ip) - UNREACHABLE" fi done echo

Test cross-site routing

echo "Testing cross-site LAN routing:" for subnet in "${LAN_SUBNETS[@]}"; do gateway="${subnet%.*}.1" if ping -c 2 -W 2 "$gateway" >/dev/null 2>&1; then echo "✓ $subnet via $gateway - ROUTABLE" else echo "✗ $subnet via $gateway - NOT ROUTABLE" fi done echo

Check routing table

echo "Active WireGuard routes:" ip route | grep "dev wg0" | while read route; do echo " $route" done echo

Test bandwidth between sites

echo "Network performance test (if iperf3 available):" if command -v iperf3 >/dev/null 2>&1; then for site in "${SITES[@]}"; do ip="${site%%:*}" name="${site##*:}" if [ "$ip" != "$(hostname -I | awk '{print $1}')" ]; then echo "Testing bandwidth to $name ($ip):" timeout 10 iperf3 -c "$ip" -t 5 2>/dev/null || echo " Bandwidth test failed or iperf3 server not running" fi done else echo " iperf3 not installed, skipping bandwidth tests" fi echo "Test completed: $(date)"
sudo chmod 755 /usr/local/bin/mesh-connectivity-test.sh
sudo /usr/local/bin/mesh-connectivity-test.sh

Common issues

SymptomCauseFix
Peers show as connected but can't pingFirewall blocking mesh trafficsudo iptables -I INPUT -s 10.100.0.0/16 -j ACCEPT
Some sites unreachable after failoverBIRD routing convergence delayCheck sudo birdc show route and wait 30 seconds
Handshake fails between peersIncorrect public keys in configVerify public keys with sudo wg show wg0 peers
Monitoring scripts not runningSystemd timers not enabledsudo systemctl enable --now wg-mesh-monitor.timer
High CPU usage from BIRDToo frequent OSPF hello intervalsIncrease hello timer to 10 seconds in BIRD config
NAT not working for site LANsMissing MASQUERADE rulessudo iptables -t nat -A POSTROUTING -s 10.100.0.0/16 -o eth0 -j MASQUERADE

Monitoring and maintenance

Set up comprehensive monitoring for your mesh network with automated alerts and performance tracking.

#!/bin/bash

Generate mesh network health report

REPORT_FILE="/tmp/mesh-health-$(date +%Y%m%d-%H%M).txt" echo "WireGuard Mesh Network Health Report" > "$REPORT_FILE" echo "Generated: $(date)" >> "$REPORT_FILE" echo "====================================" >> "$REPORT_FILE" echo >> "$REPORT_FILE"

Interface status

echo "WireGuard Interface Status:" >> "$REPORT_FILE" sudo wg show wg0 >> "$REPORT_FILE" echo >> "$REPORT_FILE"

Peer statistics

echo "Peer Connection Statistics:" >> "$REPORT_FILE" sudo wg show wg0 transfer >> "$REPORT_FILE" echo >> "$REPORT_FILE"

Routing table

echo "Active Routes:" >> "$REPORT_FILE" ip route | grep "dev wg0" >> "$REPORT_FILE" echo >> "$REPORT_FILE"

BIRD protocol status

echo "OSPF Protocol Status:" >> "$REPORT_FILE" sudo birdc show protocols >> "$REPORT_FILE" echo >> "$REPORT_FILE"

System resources

echo "System Resource Usage:" >> "$REPORT_FILE" echo "CPU: $(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | sed 's/%us,//')" >> "$REPORT_FILE" echo "Memory: $(free -m | awk 'NR==2{printf "%.1f%%", $3*100/$2}')" >> "$REPORT_FILE" echo "Disk: $(df -h / | awk 'NR==2{print $5}')" >> "$REPORT_FILE" echo >> "$REPORT_FILE"

Recent log entries

echo "Recent Mesh Events:" >> "$REPORT_FILE" tail -20 /var/log/wireguard-mesh.log >> "$REPORT_FILE" echo "Report saved to: $REPORT_FILE"

Email report if configured

if command -v mail >/dev/null 2>&1; then mail -s "WireGuard Mesh Health Report - $(hostname)" admin@example.com < "$REPORT_FILE" fi
sudo chmod 755 /usr/local/bin/mesh-health-report.sh

Run weekly health reports

echo "0 8 1 /usr/local/bin/mesh-health-report.sh" | sudo crontab -
Pro tip: Monitor WireGuard VPN server with Prometheus and Grafana dashboards for advanced metrics collection and alerting integration.

Next steps

Running this in production?

Want this handled for you? Running this at scale adds a second layer of work: capacity planning, failover drills, cost control, and on-call. Our managed platform covers monitoring, backups and 24/7 response by default.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle private cloud infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.