There is no private network between VMs. Every VM has one public IPv4 and reaches the Internet and none of our internal systems. If your machines need to talk to each other over private addresses — a database that should not listen on a public port, an NFS export, a cluster that assumes a flat internal network — you build that yourself, and WireGuard is the simplest way.
We do not filter it. The one enforced rule is anti-spoof — a VM may send only from its own IP and MAC — and a WireGuard peer does exactly that, sending UDP from its own address with your traffic inside.
Each VM gets a WireGuard interface and a private address on it; peers are
reached at their public addresses. With two or three machines, every peer
lists every other peer. With more, make one machine the hub: every other
machine lists only the hub, with AllowedIPs covering the whole private
range. The hub passes traffic between them, so enable forwarding on it
(net.ipv4.ip_forward=1, in a file under /etc/sysctl.d/ so it survives a
reboot) and let its firewall forward from wg0 to wg0.
# on each VM
umask 077
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
/etc/wireguard/wg0.conf, one per machine:
[Interface]
Address = 10.10.0.1/24 # unique per VM
ListenPort = 51820
PrivateKey = <this VM's private key>
[Peer]
PublicKey = <the other VM's public key>
AllowedIPs = 10.10.0.2/32 # addresses routed to this peer
Endpoint = peer2.example.com:51820
PersistentKeepalive = 25
systemctl enable --now wg-quick@wg0
wg show # handshakes, per peer
ping 10.10.0.2
51820/udp inbound on every peer in your
own firewall. A handshake that never completes is usually caused by this:
Harden a VM.AllowedIPs is a routing table, not an allow-list. Traffic to the
addresses listed for a peer goes to that peer. If two peers list the same
range, traffic is misrouted with no error.wg-quick sets 1420 for it.
If large transfers stall while pings work, set a lower one — MTU = 1380
in [Interface] — on every peer.Endpoint was written as an IP address. The address is bound to its VM
for that VM's life and a recreate gets a new one, so name
peers by DNS. wg-quick resolves the endpoint at start; re-run it after a
DNS change.Bind Postgres, Redis, NFS and anything else internal to the WireGuard address
alone, not 0.0.0.0, and leave the public inbound policy denying everything
the Internet is not meant to reach.
Traffic between your VMs leaves each machine's own interface, so it is metered as egress like anything else outbound; the tunnel does not change that. Keep high-volume internal traffic on one machine where you can.