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:
|
|
On Alpine:
|
|
Kernel support is required. Most modern distros include it.
2. Generate Keys
Each node needs its own keypair (run this on each node):
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
6. Enable IP Forwarding
For the mesh to route packets, enable IP forwarding:
|
|
Make it permanent:
|
|
7. Bring It Up
|
|
Check the status:
|
|
Use ping
or traceroute
to test connectivity between peers.
|
|
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:
wg-gen-web
ansible-wireguard
- Custom shell scripts + QR codes for mobile clients
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