A site-to-site VPN connects two networks so that machines on each side can reach the other privately: a VPS in Los Angeles and one in Tokyo, a dedicated server and a backup server at another provider, or an office LAN and your cloud servers. WireGuard makes this simple: two short configs, one UDP port, and minimal overhead.

Two scenarios

  1. Server to server: just the two machines need to talk (database replication, backups, private APIs).
  2. Network to network: each side is a gateway for a whole subnet, such as an office LAN (192.168.50.0/24) and a VPS’s private network (10.0.0.0/24).

The WireGuard config is nearly identical. Scenario 2 adds routing and forwarding.

Plan the addresses

Site A (VPS, Los Angeles) Site B (VPS, Tokyo)
Public IP 203.0.113.10 198.51.100.20
Tunnel IP 10.99.0.1/30 10.99.0.2/30
LAN behind it (scenario 2) 10.0.1.0/24 10.0.2.0/24
WireGuard port 51820/udp 51820/udp

Choose tunnel and LAN ranges that don’t overlap anywhere. See subnetting and CIDR.

1. Install and generate keys (both sides)

sudo apt install wireguard
umask 077
wg genkey | sudo tee /etc/wireguard/private.key | wg pubkey | sudo tee /etc/wireguard/public.key

Exchange the public keys between the two servers.

2. Site A config

/etc/wireguard/wg-s2s.conf:

[Interface]
Address = 10.99.0.1/30
ListenPort = 51820
PrivateKey = <site-A-private-key>

[Peer]
PublicKey = <site-B-public-key>
Endpoint = 198.51.100.20:51820
AllowedIPs = 10.99.0.2/32, 10.0.2.0/24
PersistentKeepalive = 25

3. Site B config

[Interface]
Address = 10.99.0.2/30
ListenPort = 51820
PrivateKey = <site-B-private-key>

[Peer]
PublicKey = <site-A-public-key>
Endpoint = 203.0.113.10:51820
AllowedIPs = 10.99.0.1/32, 10.0.1.0/24
PersistentKeepalive = 25

AllowedIPs does double duty in WireGuard. It’s the list of addresses routed into the tunnel towards this peer, and the source addresses accepted from it. For scenario 1, include only the tunnel IP. For scenario 2, add the remote LAN.

If one side is behind NAT without a public IP (an office router), omit its Endpoint on the other side. The NATed side initiates the connection, and PersistentKeepalive keeps it open.

4. Firewall and start

On both sides:

sudo ufw allow 51820/udp
sudo systemctl enable --now wg-quick@wg-s2s
sudo wg show

Restrict the UDP port to the other site’s IP if it’s static: sudo ufw allow from 198.51.100.20 to any port 51820 proto udp.

Test:

ping 10.99.0.2        # from site A

5. Network-to-network: forwarding and routes

On each gateway, enable IP forwarding:

echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-forward.conf && sudo sysctl --system

Allow forwarding between the tunnel and the LAN interface. With UFW:

sudo ufw route allow in on wg-s2s out on eth1
sudo ufw route allow in on eth1 out on wg-s2s

Other machines on each LAN must send traffic for the remote subnet to their local gateway. Either:

  • add a route on each host: ip route add 10.0.2.0/24 via 10.0.1.1 (the gateway’s LAN IP), or
  • add the route on the LAN’s default router, if it isn’t the WireGuard box, or
  • if the WireGuard box is the hosts’ default gateway already, nothing is needed.

Alternatively, masquerade traffic leaving the tunnel, so remote hosts see it as coming from the gateway. It’s simpler, but hides the original source IPs:

sudo iptables -t nat -A POSTROUTING -s 10.0.1.0/24 -o eth1 -j MASQUERADE

See port forwarding and routing and NAT explained.

Multiple sites

  • Hub and spoke: each site peers with a central hub VPS. The hub’s AllowedIPs includes each site’s LAN, and the spokes list all other LANs behind the hub. It’s simple, but traffic between spokes passes through the hub.
  • Full mesh: every site peers with every other site. It’s lower latency, but configs grow with N². Tools like Tailscale, Headscale or Netmaker automate meshes. See Headscale.

Performance and reliability

  • Throughput is mostly limited by the link and CPU. WireGuard is very efficient, and even small VPSes push hundreds of Mbps.
  • Latency equals the path between the sites. Choose locations wisely. See latency vs bandwidth.
  • If you see stalls on large transfers, lower the MTU (e.g. 1380). See MTU explained.
  • Monitor the tunnel: alert when latest handshake gets older than a few minutes (wg show wg-s2s latest-handshakes).

Quick reference

Task Command
Start / enable systemctl enable --now wg-quick@wg-s2s
Status wg show
Remote LAN routing add the LAN to the peer’s AllowedIPs
Forwarding net.ipv4.ip_forward=1 + ufw route allow
Port 51820/udp

Related: WireGuard VPN server and IPsec with strongSwan.

  • WireGuard
  • VPN
  • Networking