Build a private mesh

There is no private network, no VPC and no SDN here. Every VM has one public IPv4 and reaches the Internet and nothing of ours. 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 short way.

Nothing of ours filters it. The one enforced rule is anti-spoof — a VM may emit from its own IP and MAC — and a WireGuard peer does exactly that, sending UDP from its own address with your traffic inside.

The shape

Each VM gets a WireGuard interface and a private address on it; peers are reached at their public addresses. Two or three machines are a full mesh (every peer lists every other); past that, put one machine in the middle as a hub and give it AllowedIPs covering the whole private range.

# 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        # what this peer answers for
Endpoint   = peer2.example.com:51820
PersistentKeepalive = 25
systemctl enable --now wg-quick@wg0
wg show                                  # handshakes, per peer
ping 10.10.0.2

What goes wrong

Then close the public ports

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. A mesh that does not close the public ports has changed nothing.

What it costs

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 chatty internal traffic on the machine that generates it.