WireGuard Site-to-Site VPN Configuration

WireGuard is a modern, minimalist VPN protocol that offers better performance and security than traditional solutions like IPsec and OpenVPN. With just under 4,000 lines of code, WireGuard provides cryptographically proven security through ChaCha20 for encryption and Poly1305 for authentication. This guide covers generating cryptographic keys, configuring peer relationships, setting up routing between sites, managing firewall rules, implementing persistent keepalive, and deploying multi-site mesh networks.

Table of Contents

System Requirements

WireGuard runs on most modern Linux distributions with kernel 5.6 or later. Verify your system:

uname -r
cat /etc/os-release
nproc
free -h

For kernel versions before 5.6, WireGuard can run in userspace mode but with reduced performance.

Installation

Install WireGuard from official repositories. The installation includes both kernel module (built-in on newer kernels) and userspace tools.

For Ubuntu/Debian:

sudo apt-get update
sudo apt-get install -y wireguard wireguard-tools

For CentOS/RHEL 8+:

sudo dnf install -y wireguard-tools

For Fedora:

sudo dnf install -y wireguard-tools

For older systems without in-kernel support:

sudo apt-get install -y wireguard-dkms

Verify installation:

wg --version
ip link show type wireguard

Enable IP forwarding for routing between sites:

sudo sysctl -w net.ipv4.ip_forward=1
sudo sysctl -w net.ipv6.conf.all.forwarding=1

Make IP forwarding persistent:

sudo nano /etc/sysctl.conf

Add or uncomment:

net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

Apply changes:

sudo sysctl -p

Key Generation

WireGuard uses elliptic curve cryptography with Curve25519 keys. Generate keys for each peer.

Generate a private key:

wg genkey > private.key
cat private.key

Generate the corresponding public key:

wg pubkey < private.key > public.key
cat public.key

Generate a pre-shared key for additional security (optional):

wg genpsk > preshared.key
cat preshared.key

Secure key files:

chmod 600 private.key preshared.key

For multi-site deployments, generate unique key pairs for each site:

# Site A
mkdir -p /etc/wireguard/keys/site-a
cd /etc/wireguard/keys/site-a
wg genkey | tee private.key | wg pubkey > public.key
chmod 600 private.key

# Site B
mkdir -p /etc/wireguard/keys/site-b
cd /etc/wireguard/keys/site-b
wg genkey | tee private.key | wg pubkey > public.key
chmod 600 private.key

Basic Peer Configuration

Create a WireGuard interface with peer configuration. WireGuard interfaces are configured as regular network interfaces.

Create the WireGuard configuration file:

sudo nano /etc/wireguard/wg0.conf

Basic configuration for Site A (acting as server):

[Interface]
# Interface name
Address = 10.0.0.1/24
# Private key for this site
PrivateKey = <site-a-private-key>
# Listen port for incoming connections
ListenPort = 51820
# Optional: DNS servers
PostUp = resolvectl default-route wg0 yes
PostDown = resolvectl default-route wg0 no

[Peer]
# Public key of Site B
PublicKey = <site-b-public-key>
# Allowed IPs from this peer
AllowedIPs = 10.0.0.2/32
# Optional: Pre-shared key for additional security
PresharedKey = <optional-preshared-key>

Set restrictive permissions on the configuration file:

sudo chmod 600 /etc/wireguard/wg0.conf
sudo chown root:root /etc/wireguard/wg0.conf

Enable and bring up the interface:

sudo wg-quick up wg0

Verify the interface is active:

sudo ip addr show wg0
sudo ip link show wg0
sudo wg show

Check WireGuard status:

sudo wg show wg0

Output shows:

interface: wg0
  public key: <public-key>
  private key: (hidden)
  listening port: 51820

peer: <peer-public-key>
  allowed ips: 10.0.0.2/32
  latest handshake: (never)
  transfer: 0 B received, 0 B sent
  persistent keepalive: off

Site-to-Site Setup

Configure two sites with WireGuard for secure site-to-site connectivity.

Site A configuration (203.0.113.1, local network 192.168.1.0/24):

sudo nano /etc/wireguard/wg0.conf

Content:

[Interface]
Address = 10.0.0.1/24
PrivateKey = <site-a-private-key>
ListenPort = 51820

[Peer]
PublicKey = <site-b-public-key>
AllowedIPs = 10.0.0.2/32, 192.168.2.0/24
Endpoint = 198.51.100.5:51820
PersistentKeepalive = 25

Site B configuration (198.51.100.5, local network 192.168.2.0/24):

sudo nano /etc/wireguard/wg0.conf

Content:

[Interface]
Address = 10.0.0.2/24
PrivateKey = <site-b-private-key>
ListenPort = 51820

[Peer]
PublicKey = <site-a-public-key>
AllowedIPs = 10.0.0.1/32, 192.168.1.0/24
Endpoint = 203.0.113.1:51820
PersistentKeepalive = 25

Bring up WireGuard on both sites:

sudo wg-quick up wg0

Test connectivity:

ping -c 4 10.0.0.2  # From Site A to Site B VPN IP
ping -c 4 192.168.2.5  # From Site A to Site B local network

Verify peer connection:

sudo wg show

Should show active handshake and data transfer.

Routing Configuration

Configure routing to enable traffic between local networks at each site.

Site A: Route traffic destined for Site B's local network through WireGuard:

sudo ip route add 192.168.2.0/24 via 10.0.0.2 dev wg0

Site B: Route traffic destined for Site A's local network through WireGuard:

sudo ip route add 192.168.1.0/24 via 10.0.0.1 dev wg0

Make routing persistent with Netplan (Ubuntu/Debian):

sudo nano /etc/netplan/99-wireguard.yaml

Content:

network:
  version: 2
  renderer: networkd
  
  tunnels:
    wg0:
      mode: wireguard
      mtu: 1420
      private-key: '<site-private-key>'
      addresses:
        - 10.0.0.1/24
      peers:
        - public-key: '<peer-public-key>'
          allowed-ips:
            - 10.0.0.2/32
            - 192.168.2.0/24
          endpoint: 198.51.100.5:51820
          persistent-keepalive: 25
          
  routes:
    - to: 192.168.2.0/24
      via: 10.0.0.2
      metric: 100

Apply Netplan configuration:

sudo netplan apply

Verify routes:

ip route show

For NetworkManager (alternative):

sudo nano /etc/NetworkManager/conf.d/99-wireguard.conf

Add:

[main]
dhcp=auto

iptables Integration

Configure firewall rules to allow WireGuard traffic and route site traffic properly.

Allow WireGuard port through firewall:

sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT

Allow traffic between WireGuard peers:

sudo iptables -A FORWARD -i wg0 -j ACCEPT
sudo iptables -A FORWARD -o wg0 -j ACCEPT

Configure NAT to masquerade traffic from WireGuard interface:

sudo iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE

Save iptables rules:

sudo iptables-save > /etc/iptables/rules.v4

Configure ufw (simplified firewall):

sudo ufw allow 51820/udp
sudo ufw allow in on wg0

Alternatively, use nftables for modern systems:

sudo nano /etc/nftables.conf

Add rules:

table inet wireguard {
  chain input {
    type filter hook input priority 0;
    
    # Allow WireGuard port
    udp dport 51820 accept
  }
  
  chain forward {
    type filter hook forward priority 0;
    
    # Allow WireGuard traffic
    iifname "wg0" accept
    oifname "wg0" accept
  }
}

Apply nftables:

sudo nft -f /etc/nftables.conf

Persistent Keepalive

Configure persistent keepalive to maintain VPN connections through NAT and firewalls.

Add PersistentKeepalive to WireGuard configuration:

sudo nano /etc/wireguard/wg0.conf

Modify peer section:

[Peer]
PublicKey = <peer-public-key>
AllowedIPs = 10.0.0.2/32, 192.168.2.0/24
Endpoint = 198.51.100.5:51820
PersistentKeepalive = 25

The value (25) is in seconds. Lower values increase overhead but improve responsiveness. Typical values:

  • 0 = disabled (for endpoint initiators)
  • 10-30 = NAT traversal (most common)
  • 60+ = low-overhead keepalive

Apply changes:

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

Verify keepalive is active:

sudo wg show

Should show "persistent keepalive" interval.

Multi-Site Mesh Networks

Configure multiple sites to communicate directly in a mesh topology.

Create a mesh with 3 sites (A, B, C):

Site A (10.0.0.1/24):

[Interface]
Address = 10.0.0.1/24
PrivateKey = <site-a-private-key>
ListenPort = 51820

[Peer]
# Site B
PublicKey = <site-b-public-key>
AllowedIPs = 10.0.0.2/32, 192.168.2.0/24
Endpoint = 198.51.100.5:51820
PersistentKeepalive = 25

[Peer]
# Site C
PublicKey = <site-c-public-key>
AllowedIPs = 10.0.0.3/32, 192.168.3.0/24
Endpoint = 203.0.113.10:51820
PersistentKeepalive = 25

Site B (10.0.0.2/24):

[Interface]
Address = 10.0.0.2/24
PrivateKey = <site-b-private-key>
ListenPort = 51820

[Peer]
# Site A
PublicKey = <site-a-public-key>
AllowedIPs = 10.0.0.1/32, 192.168.1.0/24
Endpoint = 203.0.113.1:51820
PersistentKeepalive = 25

[Peer]
# Site C
PublicKey = <site-c-public-key>
AllowedIPs = 10.0.0.3/32, 192.168.3.0/24
Endpoint = 203.0.113.10:51820
PersistentKeepalive = 25

Site C (10.0.0.3/24):

[Interface]
Address = 10.0.0.3/24
PrivateKey = <site-c-private-key>
ListenPort = 51820

[Peer]
# Site A
PublicKey = <site-a-public-key>
AllowedIPs = 10.0.0.1/32, 192.168.1.0/24
Endpoint = 203.0.113.1:51820
PersistentKeepalive = 25

[Peer]
# Site B
PublicKey = <site-b-public-key>
AllowedIPs = 10.0.0.2/32, 192.168.2.0/24
Endpoint = 198.51.100.5:51820
PersistentKeepalive = 25

Bring up all sites:

sudo wg-quick up wg0

Test mesh connectivity:

# From Site A
ping 10.0.0.2  # Site B VPN IP
ping 10.0.0.3  # Site C VPN IP
ping 192.168.2.5  # Site B local network
ping 192.168.3.10  # Site C local network

Verify mesh topology:

sudo wg show
# Should show all peers with active handshakes

Performance Tuning

Optimize WireGuard performance for high-bandwidth scenarios.

Configure MTU (Maximum Transmission Unit):

sudo ip link set dev wg0 mtu 1420

Or in Netplan:

tunnels:
  wg0:
    mtu: 1420

Enable UDP segmentation offload:

sudo ethtool -K eth0 tx-udp_tnl-segmentation on

Monitor performance:

sudo wg show

Shows real-time packet statistics and transfer rates.

Use iperf to benchmark throughput:

# Site B (server)
iperf3 -s

# Site A (client)
iperf3 -c 10.0.0.2

Adjust ring buffer sizes for network interface:

sudo ethtool -G eth0 rx 4096 tx 4096

Troubleshooting

Diagnose common WireGuard issues.

Enable debug logging:

sudo modprobe wireguard
sudo echo "module wireguard +p" > /sys/kernel/debug/dynamic_debug/control
journalctl -u systemd-modules-load -xe

Verify interface is up:

ip link show dev wg0
ip addr show dev wg0

Test connectivity through WireGuard:

ping -c 4 10.0.0.2
traceroute 10.0.0.2
mtr 10.0.0.2

Check open ports:

sudo ss -ulnp | grep wireguard
sudo netstat -ulnp | grep 51820

View WireGuard status details:

sudo wg show all
sudo wg show wg0 peers
sudo wg show wg0 allowed-ips

Verify pre-shared key if configured:

sudo cat /etc/wireguard/wg0.conf | grep -i preshared

Check firewall rules:

sudo iptables -L INPUT -n | grep 51820
sudo iptables -L FORWARD -n | grep wg
sudo ufw status

Conclusion

WireGuard provides a modern, efficient VPN solution for site-to-site connectivity with minimal overhead and strong cryptographic security. By following this guide, you've generated cryptographic keys, configured peer relationships, set up direct site-to-site tunnels, implemented routing for transparent access to remote networks, integrated firewall rules, deployed persistent keepalive for NAT traversal, and built mesh networks with multiple sites. WireGuard's simplicity and performance make it ideal for connecting infrastructure, data centers, and branch offices. Regular monitoring and security updates ensure continued reliable operation of your site-to-site network infrastructure.