WireGuard on VyOS: Production Configuration for Site-to-Site and Road Warriors

WireGuard is simple by design — a few keys, some IP addresses, and you’re connected. But “connected” and “production-ready” are different things. Intermittent disconnections, mysterious packet loss, traffic leaking outside the tunnel — these happen when you skip the fundamentals.

The two things that make WireGuard stable: correct MTU and clear routing policy. Get these right, and WireGuard becomes boring (in the best way).

WireGuard Basics on VyOS

VyOS treats WireGuard as a first-class interface. Configuration is straightforward:

Terminal window
configure
# Generate keypair (or use existing)
run generate pki wireguard key-pair
# Save the output:
# Private-key: <base64 private key>
# Public-key: <base64 public key>

Store the private key securely. The public key is what you share with peers.

Site-to-Site Configuration

Two VyOS routers connecting their networks. Site A (10.1.0.0/24) connects to Site B (10.2.0.0/24).

Site A Configuration

Terminal window
configure
# WireGuard interface
set interfaces wireguard wg0 address '10.255.255.1/30'
set interfaces wireguard wg0 description 'Site-to-Site to Site B'
set interfaces wireguard wg0 port '51820'
set interfaces wireguard wg0 private-key '<site-a-private-key>'
# Peer configuration (Site B)
set interfaces wireguard wg0 peer site-b public-key '<site-b-public-key>'
set interfaces wireguard wg0 peer site-b allowed-ips '10.255.255.2/32'
set interfaces wireguard wg0 peer site-b allowed-ips '10.2.0.0/24'
set interfaces wireguard wg0 peer site-b endpoint 'site-b.example.com:51820'
set interfaces wireguard wg0 peer site-b persistent-keepalive '25'
# Route to Site B's network
set protocols static route 10.2.0.0/24 interface wg0
# Firewall: allow WireGuard traffic
set firewall ipv4 name WAN-LOCAL rule 60 action 'accept'
set firewall ipv4 name WAN-LOCAL rule 60 protocol 'udp'
set firewall ipv4 name WAN-LOCAL rule 60 destination port '51820'
commit

Site B Configuration

Mirror configuration with swapped keys and addresses:

Terminal window
configure
set interfaces wireguard wg0 address '10.255.255.2/30'
set interfaces wireguard wg0 description 'Site-to-Site to Site A'
set interfaces wireguard wg0 port '51820'
set interfaces wireguard wg0 private-key '<site-b-private-key>'
set interfaces wireguard wg0 peer site-a public-key '<site-a-public-key>'
set interfaces wireguard wg0 peer site-a allowed-ips '10.255.255.1/32'
set interfaces wireguard wg0 peer site-a allowed-ips '10.1.0.0/24'
set interfaces wireguard wg0 peer site-a endpoint 'site-a.example.com:51820'
set interfaces wireguard wg0 peer site-a persistent-keepalive '25'
set protocols static route 10.1.0.0/24 interface wg0
set firewall ipv4 name WAN-LOCAL rule 60 action 'accept'
set firewall ipv4 name WAN-LOCAL rule 60 protocol 'udp'
set firewall ipv4 name WAN-LOCAL rule 60 destination port '51820'
commit

Validation:

Terminal window
# Check interface status
show interfaces wireguard wg0
# Check peer status
show wireguard peers
# Test connectivity
ping 10.255.255.2 # Tunnel endpoint
ping 10.2.0.1 # Remote LAN (from Site A)

Road Warrior Configuration (Mobile Clients)

Roaming clients that connect from anywhere. The VyOS router acts as the VPN server.

VyOS Server Configuration

Terminal window
configure
set interfaces wireguard wg0 address '10.10.0.1/24'
set interfaces wireguard wg0 description 'Road Warrior VPN'
set interfaces wireguard wg0 port '51820'
set interfaces wireguard wg0 private-key '<server-private-key>'
# Client 1 (laptop)
set interfaces wireguard wg0 peer laptop public-key '<laptop-public-key>'
set interfaces wireguard wg0 peer laptop allowed-ips '10.10.0.10/32'
# Client 2 (phone)
set interfaces wireguard wg0 peer phone public-key '<phone-public-key>'
set interfaces wireguard wg0 peer phone allowed-ips '10.10.0.11/32'
# Allow VPN clients to access LAN and internet
set firewall group network-group VPN-CLIENTS network '10.10.0.0/24'
# NAT for VPN clients going to internet
set nat source rule 200 outbound-interface name 'eth0'
set nat source rule 200 source address '10.10.0.0/24'
set nat source rule 200 translation address 'masquerade'
# Firewall: allow WireGuard
set firewall ipv4 name WAN-LOCAL rule 60 action 'accept'
set firewall ipv4 name WAN-LOCAL rule 60 protocol 'udp'
set firewall ipv4 name WAN-LOCAL rule 60 destination port '51820'
commit

Client Configuration (wg0.conf)

For laptop/phone using standard WireGuard client:

[Interface]
PrivateKey = <laptop-private-key>
Address = 10.10.0.10/32
DNS = 10.0.0.1
[Peer]
PublicKey = <server-public-key>
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.example.com:51820
PersistentKeepalive = 25

AllowedIPs = 0.0.0.0/0 means full tunnel — all traffic through VPN.

Split Tunnel vs Full Tunnel

Full tunnel: All client traffic goes through VPN

  • AllowedIPs = 0.0.0.0/0, ::/0
  • Pros: All traffic protected, consistent IP
  • Cons: Higher latency, more bandwidth on VPN server

Split tunnel: Only specific traffic through VPN

  • AllowedIPs = 10.0.0.0/8, 192.168.0.0/16
  • Pros: Better performance, less server load
  • Cons: Some traffic exposed, DNS leaks possible

Split Tunnel Client Example

[Interface]
PrivateKey = <private-key>
Address = 10.10.0.10/32
[Peer]
PublicKey = <server-public-key>
AllowedIPs = 10.0.0.0/8, 10.10.0.0/24
Endpoint = vpn.example.com:51820
PersistentKeepalive = 25

Only traffic to 10.x.x.x goes through VPN. Everything else uses local internet.

The MTU Problem

WireGuard encapsulates packets, adding overhead. If your MTU is too high, packets get fragmented or dropped. Symptoms:

  • SSH works, HTTPS fails
  • Small requests work, large transfers hang
  • Intermittent “connection reset”

Calculate Correct MTU

Standard Ethernet MTU: 1500 WireGuard overhead: 60 bytes (IPv4) or 80 bytes (IPv6) Safe WireGuard MTU: 1420 (IPv4) or 1400 (IPv6)

Terminal window
configure
set interfaces wireguard wg0 mtu '1420'
commit

Test MTU

Terminal window
# From client, test path MTU to a host through the tunnel
ping -M do -s 1392 10.0.0.1

-M do prevents fragmentation. -s 1392 = 1392 payload + 28 header = 1420. If it works, MTU is correct. If not, lower it.

For connections through multiple NATs or tunnels, you might need 1380 or even lower.

Kill Switch: Preventing Leaks

A kill switch ensures traffic can’t leak if the VPN disconnects. On VyOS server, you control routing. On clients, configure the client app or OS firewall.

Server-Side: Ensure Clients Use VPN

If clients should only access internet through VPN:

Terminal window
# Already covered by NAT rule - VPN clients are masqueraded
# No direct route from VPN subnet to internet except through NAT

Client-Side Kill Switch (Linux)

Terminal window
# Allow only WireGuard and local traffic
iptables -A OUTPUT -o wg0 -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -p udp --dport 51820 -j ACCEPT
iptables -A OUTPUT -j DROP

Or use WireGuard’s PostUp/PostDown scripts in the config.

Persistent Keepalive: When to Use It

WireGuard is silent when idle — no traffic means no packets. This causes problems:

  • NAT mappings expire (typically 30-60 seconds)
  • Stateful firewalls drop the “connection”
  • You can’t initiate connections TO the client

persistent-keepalive '25' sends a keepalive every 25 seconds, keeping NAT/firewall state alive.

Use it when:

  • Client is behind NAT
  • Either side has stateful firewall
  • You need to reach the client from the server

Skip it when:

  • Both sides have static public IPs
  • No NAT involved
  • Saving minimal bandwidth matters

Debugging WireGuard

Check Interface Status

Terminal window
show interfaces wireguard wg0

Should show UP state and assigned address.

Check Peer Handshakes

Terminal window
show wireguard peers

Shows last handshake time. If “never” or very old, tunnel isn’t working.

Check Keys Match

Most common issue: public/private key mismatch. Verify:

  • Server has client’s public key
  • Client has server’s public key
  • No copy-paste errors (check for trailing spaces)

Check Firewall

Terminal window
show firewall ipv4 name WAN-LOCAL

Ensure UDP 51820 is allowed inbound.

Check Routing

Terminal window
show ip route

Routes through wg0 should exist for peer’s allowed-ips.

Monitor Traffic

Terminal window
sudo tcpdump -i wg0 -n

Should see traffic when peers communicate.

Common Issues

SymptomCauseFix
No handshakeKey mismatch or blocked portVerify keys, check firewall
Handshake but no trafficRouting or allowed-ips wrongCheck routes match allowed-ips
Works then diesNAT timeoutEnable persistent-keepalive
Large transfers failMTU too highLower MTU to 1420 or less
One direction worksAsymmetric allowed-ipsBoth sides need matching allowed-ips

Production Checklist

Before calling it production-ready:

  • MTU set correctly (1420 or tested value)
  • Persistent keepalive enabled if behind NAT
  • Firewall allows WireGuard port (UDP 51820)
  • Routes exist for all allowed-ips
  • Keys are backed up securely
  • NAT configured if clients need internet access
  • DNS configured for full-tunnel clients
  • Kill switch configured if leak prevention needed

Complete Road Warrior Example

Terminal window
# === WireGuard Interface ===
set interfaces wireguard wg0 address '10.10.0.1/24'
set interfaces wireguard wg0 mtu '1420'
set interfaces wireguard wg0 port '51820'
set interfaces wireguard wg0 private-key '<server-private-key>'
# === Peers ===
set interfaces wireguard wg0 peer laptop public-key '<key>'
set interfaces wireguard wg0 peer laptop allowed-ips '10.10.0.10/32'
set interfaces wireguard wg0 peer phone public-key '<key>'
set interfaces wireguard wg0 peer phone allowed-ips '10.10.0.11/32'
# === NAT for VPN clients ===
set nat source rule 200 outbound-interface name 'eth0'
set nat source rule 200 source address '10.10.0.0/24'
set nat source rule 200 translation address 'masquerade'
# === Firewall ===
set firewall ipv4 name WAN-LOCAL rule 60 action 'accept'
set firewall ipv4 name WAN-LOCAL rule 60 protocol 'udp'
set firewall ipv4 name WAN-LOCAL rule 60 destination port '51820'
# VPN clients to LAN (apply via forward filter)
set firewall ipv4 name VPN-TO-LAN default-action 'accept'
set firewall ipv4 forward filter rule 50 inbound-interface name 'wg0'
set firewall ipv4 forward filter rule 50 action 'jump'
set firewall ipv4 forward filter rule 50 jump-target 'VPN-TO-LAN'
# === DNS for VPN clients ===
set service dns forwarding listen-address '10.10.0.1'
set service dns forwarding allow-from '10.10.0.0/24'

The Lesson

WireGuard becomes stable after addressing two things:

  1. MTU: Set it explicitly to 1420 or lower. Don’t rely on automatic MTU discovery — it often fails through NAT and firewalls.

  2. Routing policy: Be explicit about what traffic goes where. allowed-ips controls both routing AND cryptographic acceptance. If it’s not in allowed-ips, it won’t be accepted even if routed correctly.

Everything else — keepalives, firewall rules, NAT — follows logically from the use case. But MTU and routing policy are where most WireGuard problems live. Fix those, and the rest falls into place.