Configuration is not comprehension. But this one’s worth understanding.

WireGuard is the cleanest VPN protocol I’ve seen: No legacy baggage, no userland daemons, no certificates. Just modern crypto and a kernel module that does what it says on the tin. It’s codebase is ~4,000 lines of C, versus hundreds of thousands of lines for IPsec or OpenVPN.

But once you try to build a full mesh with it — peer-to-peer, no central server — things get… educational.

This post walks through exactly that: setting up a simple WireGuard mesh between multiple Linux nodes, the kind you might use for remote development, edge compute, or bootstrapping your own private overlay network.

Why Mesh?

  • No single point of failure.
  • Direct peer-to-peer paths.
  • Works well for edge nodes and nomadic devices.
  • More fun.

1. Install WireGuard

On Ubuntu:

1
2
sudo apt update
sudo apt install wireguard

On Alpine:

1
apk add wireguard-tools

Kernel support is required. Most modern distros include it.

2. Generate Keys

Each node needs its own keypair (run this on each node):

1
2
wg genkey | tee privatekey | wg pubkey > publickey
chmod 600 privatekey

You’ll need to share public keys between nodes. Keep private keys private (obviously).

  • Each peer needs your public key in their WireGuard config’s [Peer] section.
  • Similarly, you’ll need each peer’s public key in your config.

3. Choose Internal IPs

Pick an internal subnet (e.g. 10.42.0.0/24) and assign each peer a unique address.

Example mesh:

Node Internal IP Public Key
node-a 10.42.0.1 pubkey-a
node-b 10.42.0.2 pubkey-b
node-c 10.42.0.3 pubkey-c

You’ll need to update each peer config with the others’ IPs and keys.

4. Basic Config

Each node gets a /etc/wireguard/wg0.conf. If you’re using this on the public internet and not just a LAN, replace the endpoints with your real public IP/hostnames.

Your private key goes into the [Interface] section. Each network peer gets its own [Peer] section. For example, if your node is node-a and you’ve got pubkey-b from node-b and pubkey-c from node-c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[Interface]
PrivateKey = <ACTUAL_PASTED_PRIVATE_KEY>
Address = 10.42.0.1/24
ListenPort = 51820

[Peer]
PublicKey = <ACTUAL_PASTED_PUBLIC_KEY_OF_NODE-B>
Endpoint = node-b.example.com:51820
AllowedIPs = 10.42.0.2/32
PersistentKeepalive = 25

[Peer]
PublicKey = <ACTUAL_PASTED_PUBLIC_KEY_OF_NODE-C>
Endpoint = node-c.example.com:51820
AllowedIPs = 10.42.0.3/32
PersistentKeepalive = 25

Each peer points directly to the others. AllowedIPs tells the kernel where to route traffic.

If you prefer, you can load the keys with wg set rather than hard-pasting them in the config file.

1
wg set wg0 private-key ./privatekey

Tip: Use hostnames if public IPs change. Use DDNS or static DNS for dynamic nodes.

5. MTU Matters

WireGuard defaults to MTU 1420. You will hit issues if you’re using tunnels or running over weird networks (e.g. mobile, cloud NAT, etc).

Check your path MTU:

1
ip link show wg0

Lower it if needed. I found that even 1380 was too high, and made web pages (other than very short ones like a Google search) insanely slow or impossible to load. For me, 1280 was good. I didn’t try in between values. This was with more than one layer of VPN and NAT between my machines and the internet.

1
2
[Interface]
MTU = 1280

6. Enable IP Forwarding

For the mesh to route packets, enable IP forwarding:

1
sudo sysctl -w net.ipv4.ip_forward=1

Make it permanent:

1
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf

7. Bring It Up

1
2
sudo sysctl -w net.ipv4.ip_forward=1      # mesh routing (optional but recommended)
sudo wg-quick up wg0

Check the status:

1
sudo wg show

Use ping or traceroute to test connectivity between peers.

1
ping -c2 10.42.0.2 && ping -c2 10.42.0.3   # from node-a

8. Troubleshooting

  • 🔌 No handshake? Check firewall and NAT rules. Check for UDP blocked or wrong endpoint/port.
  • 🔑 Keys mismatched? Public key must match the private key used on the other node.
  • 🤝 Handshakes OK, no traffic: AllowedIPs too broad/narrow or wrong; try /32 per peer first.
  • ➡️ Works one way: NAT keepalive—ensure PersistentKeepalive = 25 on the side behind NAT.
  • 🚦 Stalls, or Packets drop? MTU. Always MTU. Try MTU = 1380 (or lower if you have nested tunnels).
  • 🐢 Slow or unreliable? Try adding PersistentKeepalive = 25 to each peer to keep NAT holes open.

Bonus: Full Mesh Automation

You can automate key generation, config templating, and peer discovery with tools like:

But it’s worth doing it by hand first. That’s how you learn.


Signals You’re Sending

✅ IP-level troubleshooting
✅ Peer-to-peer network config
✅ Kernel module usage
✅ MTU + routing awareness
✅ “Not afraid to edit /etc” energy


Love requires freedom.
Freedom is knowing what your stack is actually doing.

See also: How to Set Up a Free WireGuard VPN on Oracle Cloud