WireGuard site-to-site between two LYLIX VPSes — config, routing, MTU
WireGuard is the cleanest way to connect two VPSes — or two sites — over an encrypted tunnel. Configuration is a few lines of text per side. This article walks the site-to-site setup between two LYLIX VPSes, with the routing and MTU notes that trip people up.
Scenario
Two VPSes:
- VPS-A in one region. Public IP:
203.0.113.10. We'll give it WireGuard IP10.0.0.1. - VPS-B in another. Public IP:
198.51.100.20. WireGuard IP10.0.0.2.
Goal: services on VPS-A can reach services on VPS-B by 10.0.0.2 and vice versa, without exposing those services on public IPs.
Install
WireGuard ships with current Linux kernels. The userspace tools:
# Debian/Ubuntu
apt install wireguard
# AlmaLinux/Rocky
dnf install wireguard-tools
Generate keys
On each VPS:
cd /etc/wireguard
umask 077
wg genkey | tee privatekey | wg pubkey > publickey
cat privatekey publickey
Keep the private key local to each VPS. Exchange public keys between the two.
VPS-A config
/etc/wireguard/wg0.conf on VPS-A:
[Interface]
PrivateKey = <vps-a-private-key>
Address = 10.0.0.1/24
ListenPort = 51820
[Peer]
PublicKey = <vps-b-public-key>
Endpoint = 198.51.100.20:51820
AllowedIPs = 10.0.0.2/32
PersistentKeepalive = 25
VPS-B config
/etc/wireguard/wg0.conf on VPS-B:
[Interface]
PrivateKey = <vps-b-private-key>
Address = 10.0.0.2/24
ListenPort = 51820
[Peer]
PublicKey = <vps-a-public-key>
Endpoint = 203.0.113.10:51820
AllowedIPs = 10.0.0.1/32
PersistentKeepalive = 25
Start the tunnel
# On both VPSes
wg-quick up wg0
systemctl enable wg-quick@wg0
Verify
# Show tunnel status
wg
# Ping the other side
ping -c 3 10.0.0.2 # from VPS-A
ping -c 3 10.0.0.1 # from VPS-B
Both pings should succeed within seconds.
Firewall
Open UDP 51820 on both VPSes. With nftables:
nft add rule inet filter input udp dport 51820 accept
With iptables:
iptables -A INPUT -p udp --dport 51820 -j ACCEPT
Allow forwarded WireGuard traffic if you'll route through the tunnel (see "Extending to subnets" below).
Binding services to the WireGuard IP
To make a service on VPS-A reachable only from VPS-B (not publicly):
# nginx
server {
listen 10.0.0.1:80;
# ...
}
# A simple service via systemd
[Service]
ExecStart=/usr/bin/myservice --bind 10.0.0.1:8080
Now the service is reachable from VPS-B (via 10.0.0.1:8080) and not from the public internet.
Extending to LAN subnets
If VPS-A is a gateway for a 192.168.1.0/24 LAN and you want VPS-B's traffic to reach that LAN:
- Update VPS-A's wg0.conf:
AllowedIPs = 10.0.0.2/32, 192.168.1.0/24 - Wait — that's wrong. AllowedIPs on VPS-A's peer is what VPS-B is responsible for. VPS-B doesn't have the LAN subnet; VPS-A does. We want the reverse: on VPS-B, add 192.168.1.0/24 to AllowedIPs for the VPS-A peer:
[Peer] PublicKey = <vps-a-public-key> Endpoint = 203.0.113.10:51820 AllowedIPs = 10.0.0.1/32, 192.168.1.0/24 PersistentKeepalive = 25 - Enable IP forwarding on VPS-A:
sysctl -w net.ipv4.ip_forward=1 # Persist in /etc/sysctl.conf - NAT outbound traffic from VPS-B's WireGuard IP through VPS-A's LAN interface (with iptables, nft equivalent):
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth1 -j MASQUERADE
Now VPS-B can reach 192.168.1.x via the tunnel.
MTU — the silent breakage
WireGuard packets add overhead. The default MTU inside the tunnel needs to be smaller than the path MTU between the peers.
Default MTU calculation:
- Path MTU: typically 1500.
- WireGuard overhead: 80 bytes (IPv4) or 100 bytes (IPv6).
- Tunnel MTU: 1420 (IPv4) or 1400 (IPv6).
wg-quick sets MTU 1420 by default. If your VPSes' upstream path is something other than 1500 (PPPoE, MPLS, tunneled), you need to lower the tunnel MTU:
[Interface]
MTU = 1380
Symptoms of MTU misconfiguration: small packets work (ping), large packets (file transfer, web pages with images) hang or transfer extremely slowly.
Find the path MTU between the two VPSes:
# From VPS-A, ping VPS-B's public IP with don't-fragment
ping -M do -s 1472 198.51.100.20
# Adjust size until you find the largest that works
# Tunnel MTU = (working size) + 28 - WG overhead
Three or more peers
WireGuard's model is a hub-and-spoke or mesh of peer-to-peer connections, not a "network" with members. For three+ VPSes:
- Hub-and-spoke: one VPS is the hub; all traffic between spokes routes through it. Simplest config but routes via the hub.
- Full mesh: every VPS has a peer config for every other. Best performance, more config to maintain.
For more than a handful of nodes, consider Headscale (see Headscale on a LYLIX VPS) — same WireGuard underneath but with coordination automation.
Operational tips
- PersistentKeepalive every 25 seconds keeps the NAT pinhole open if either side is behind NAT (rare for VPS to VPS, but practice the habit).
- WireGuard's "AllowedIPs" doubles as routing table — only traffic to those IPs goes through the tunnel.
- If you change public keys, rotate on both ends in the same window; mismatched keys silently drop traffic.
- Monitor handshake age:
wg show wg0 latest-handshakes. Stale handshakes mean the tunnel is broken.
Also Read
Powered by WHMCompleteSolution