← all posts

· Networking · 9 min read

Home, office, and a bastion in the cloud — one WireGuard config

The goal

I want my laptop, phone, and work PC to behave like they're always on my home LAN and on the office LAN simultaneously — regardless of where they actually are in the world. That means:

This is doable, but it took me a few iterations of wrong designs before I landed on the shape that finally just works. This post is that shape, in short form — the long-form walkthrough lives in my HowTos repo; the architecture is worth explaining on its own.

WireGuard bastion topology

Why neither LAN can be the hub

The obvious shape is "put WireGuard on my home router, have devices dial in". It doesn't work if your ISP hands you a CG-NAT address — there's no public IP for clients to dial. Even when there is one, it drifts; your home IP changes during reboots, outages, provider maintenance. Your WireGuard client configs go stale quietly. Office networks have the same problem, usually worse (firewalls you don't own).

Both LANs are behind something. Nothing can reach them from the outside without help.

So the fix is to put the hub somewhere public, and have both LANs dial out to it.

The three-piece architecture

Three long-running WireGuard peers:

  1. Bastion — a small cheap VM on a public cloud (I use DigitalOcean; any provider works). This is the only node with a public IP and an open UDP port. Its job is to shuffle packets between the other two LANs.
  2. Home server — a machine inside the home LAN. Dials out to the bastion, exposes the home subnet (say 10.80.0.0/16).
  3. Office server — same, but for the office subnet (10.50.0.0/16).

Then any number of user devices — laptop, phone, work PC — connect to the bastion too. Each device gets its own VPN IP and can reach both LANs through the bastion.

          ┌───────────────────────────┐
          │   bastion (public IP)     │
          │   wg0 — 10.99.0.3/24      │
          └──────────────┬────────────┘
                 hub     │     hub
          ┌──────────────┼──────────────┐
          │              │              │
 ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
 │ home-server  │  │ office-server│  │  laptop      │
 │ wg0: .2      │  │ wg0: .1      │  │  wg0: .101   │
 │ lan 10.80/16 │  │ lan 10.50/16 │  └──────────────┘
 └──────────────┘  └──────────────┘   (plus phone, etc.)

All WireGuard peers live on a private VPN subnet I chose for myself — 10.99.0.0/24 — that doesn't overlap with anything real.

The magic is in AllowedIPs

Every other line of every config is boilerplate. AllowedIPs is what makes the whole thing route correctly.

Bastion's config

On the bastion, each peer entry tells WireGuard what subnets live behind that peer:

[Peer]   # Home server
PublicKey   = <home pubkey>
AllowedIPs  = 10.99.0.2/32, 10.80.0.0/16

[Peer]   # Office server
PublicKey   = <office pubkey>
AllowedIPs  = 10.99.0.1/32, 10.50.0.0/16

[Peer]   # User device (repeat per device)
PublicKey   = <laptop pubkey>
AllowedIPs  = 10.99.0.101/32

The 10.80.0.0/16 and 10.50.0.0/16 entries are what turn two opaque peers into subnet gateways. Any packet the bastion receives for 10.80.x.x gets steered into the home-server tunnel; anything for 10.50.x.x goes to the office-server tunnel. Packets for 10.99.0.101 go to the laptop. That's the whole routing table.

Client's config (one file for everything)

On every user device — laptop, phone, work PC — the [Peer] block points at the bastion and declares every subnet it wants to reach:

[Peer]   # Bastion
PublicKey   = <bastion pubkey>
Endpoint    = bastion.awkto.dev:51820
AllowedIPs  = 10.99.0.0/24, 10.80.0.0/16, 10.50.0.0/16
PersistentKeepalive = 25

That's the "one config" part. The laptop has no idea how to reach the home LAN directly. It just knows: for any of those three subnets, push the packet into the WireGuard tunnel. The bastion sorts out which of the two LANs it actually belongs to.

The home and office servers have a mirror version pointing at the bastion. NAT on each server makes traffic from 10.99.x.x appear as coming from the server's own LAN IP, so replies find their way back through the tunnel.

WireGuard handshake status on a laptop

The "physical network" gotcha — and the elegant fix

If you set this up and then walk into your home with WireGuard still running, you hit a weird problem: your laptop routes home-LAN traffic through the tunnel (to the bastion, to the home-server, back to the home LAN) even though it's literally on the home Wi-Fi sitting next to the home server. That's a huge, unnecessary detour.

The knee-jerk fix is "turn WireGuard off when you're home". That's what I did for a month. It's fine, but you keep forgetting.

The clever fix, which I ripped straight out of my own HowTo notes after failing to remember why I'd written it down:

Network routing always prefers the more specific route. So if the WireGuard AllowedIPs is 10.80.0.0/15 (less specific) and the physical Wi-Fi route is 10.80.0.0/16 (more specific), traffic goes out the Wi-Fi interface whenever you're physically there — and falls back to WireGuard when you're not.

One character edit (/16 → /15) turns "always tunnelled" into "automatically shortest-path". The catch is that the next adjacent /16 has to not actually be in use anywhere — for 10.80.0.0/15 that means no one is using 10.81.0.0/16. For RFC1918 home networks you control, easy to guarantee.

So now I literally never turn WireGuard off. It does the right thing depending on where I am. That alone feels like a magic trick.

Phones get QR codes, not .conf files

Pushing WireGuard configs onto a phone is a whole friction moment — transferring files, finding the import button, hoping you typed the endpoint right. The official WireGuard mobile apps solve it with QR scanning.

sudo apt install qrencode
qrencode -t ANSIUTF8 < /etc/wireguard/peer-configs/user1-phone.conf

The QR blob renders straight in my terminal. Open WireGuard on the phone, tap "Scan from QR code", point at the screen. The phone imports the config in two seconds and is on the VPN before I've finished my sentence.

QR code rendered in terminal for phone import

What this unlocks, in practice

Results

This setup has been running for about eight months and I've edited the configs exactly zero times since. I've added new user devices twice (just a new peer block and a new .conf), and I've swapped the home server hardware once (regenerated keys, pushed the new public key into the bastion config, no other changes). The architecture is honestly the kind of thing you set up once and then forget about — in a good way.

The deep lesson, again: put your hub somewhere public, and dial out from the private places. Everything follows.