Knowledgebase › WireGuard site-to-site between two LYLIX VPSes — config, routing, MTU

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 IP 10.0.0.1.
  • VPS-B in another. Public IP: 198.51.100.20. WireGuard IP 10.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

« « Back

Powered by WHMCompleteSolution