The simplest possible design for remote access into a small self-hosted setup is one tunnel: everyone and everything that needs to reach in gets a peer on the same VPN, and whatever access control matters happens after that, at the application or firewall layer. It’s fewer moving parts, one config to reason about, one interface to bring up. For a while that simplicity looks like the right trade.
It stops looking right the moment you think through what a single shared tunnel actually couples together: the blast radius of every access decision becomes the same blast radius. Revoke a peer, and you’ve revoked whatever level of access that peer had. But if “whatever level of access” spans everything from read-only dashboards to root on the box that runs everything else, then every revocation decision is now also a decision about the highest-privilege thing on that network, whether you meant it to be or not. There’s no way to turn down day-to-day general access without the same lever also touching admin access, because they’re the same lever.
Two tunnels, two blast radii
The fix isn’t a fancier access control layer bolted onto one VPN: it’s not routing privileged and general traffic down the same pipe in the first place. Privileged admin access and general access run over two entirely separate tunnels. Different peer sets, different keys, different interfaces, and critically, no shared config file where touching one accidentally touches the other. The two tunnels don’t need to know about each other at all. The most consequential place this split gets applied is the management plane itself, where the second tunnel isn’t just about limiting blast radius between two levels of user access: it’s the only path to the layer that can reboot the hypervisor underneath everything else.
The payoff shows up specifically at the moment of revocation, which is the moment access control designs are actually tested. If a general-access device is lost or compromised, pulling it out of the general tunnel’s peer list is a change scoped entirely to that tunnel. It cannot, by construction, touch the admin tunnel’s peer list, because that’s a different file, a different interface, a different set of keys. You don’t have to reason about whether removing a peer from one tunnel has any side effect on the other: there’s no shared state for a side effect to travel through. The isolation isn’t a policy you have to remember to enforce; it’s a property of the topology.
This matters more for a WireGuard-style setup specifically than it would for some VPN designs, because WireGuard doesn’t give you a built-in notion of “downgrade this session’s privilege” or a central revocation list you consult at connect time. A peer is either configured on an interface or it isn’t: access is granted and removed by editing peer configuration, not by a runtime authorization check that some session token gets revoked centrally. That’s a deliberate part of the protocol’s design: instead of certificate chains and CRLs, every peer is just a public key entry, and removing access means removing that entry. It’s a genuinely simple mechanism, but simple mechanisms don’t compose safety on their own: they just do exactly what you configured, which means the configuration boundary has to be where the safety lives. Put privileged and general peers in the same interface’s config and the mechanism will happily let a general-access change graze admin access, because as far as WireGuard is concerned there’s only one tunnel and one peer list to edit.
Why the peer is a device, not a person
The second piece follows from the same logic taken one level down. Peer identity in this setup is per-device, not per-person. Alberto’s laptop is a peer. Alberto’s phone is a different peer. If one person legitimately uses access from three different devices, that’s three separate peer entries, three separate keys, not one shared identity multiplexed across them.
This looks like more bookkeeping than a per-person model, and it is, marginally. What it buys back is precision at exactly the moment precision matters: a lost or compromised device gets revoked as itself, without touching any other device that same person owns. If identity were per-person instead, losing one device would force a choice between leaving a security gap open (a stolen device with valid person-level credentials still on the network) or revoking that person’s access entirely and knocking out their other, uncompromised devices along with it. Neither option is good, and per-device identity is what removes the need to choose between them. It’s the same principle as the two-tunnel split, applied to the smallest unit of trust the system has: don’t let a single credential span more risk surface than the thing it’s actually meant to authorize.
This does mean key rotation and device provisioning are recurring, ongoing tasks rather than a one-time setup: every new device is a new key pair and a new peer entry, and losing a device means a rotation for that entry specifically. That’s real overhead. It’s overhead that scales with the number of devices, not with the number of people, which on a small personal setup keeps it bounded, and it’s overhead paid at the moment of adding or losing a device, which is a rare, deliberate event, rather than overhead paid continuously the way a shared-tunnel design’s ongoing risk exposure would be.
The cost side of the ledger
It’s worth being honest about what two tunnels actually cost, because the design only makes sense if that cost is smaller than the risk it removes. Every device that needs both levels of access (which in a one-person setup is usually just the operator’s own primary machine) needs two interfaces up, two sets of keys managed, two configs kept straight. That’s not free. It’s also not the overhead it might sound like: bringing up a second WireGuard interface is a config file and an up/down toggle, not a second piece of infrastructure to run. The operational cost is closer to “one more thing to remember exists” than “one more system to maintain.”
The more interesting cost is conceptual, not mechanical: with two tunnels, there’s no single place that answers “what can this device reach right now.” You have to check which tunnels a device is a peer on to know its actual reach, rather than reading one config and knowing everything. For a small number of devices that’s a short mental list. It stops being a short mental list if the device count grows a lot, at which point the two-tunnel model would want some kind of inventory (a simple table of device, tunnel, purpose) rather than relying on memory the way it can at small scale. That’s a real limit on how far this design scales as-is, and worth flagging honestly rather than pretending the two-tunnel split is a complete access-control system on its own.
Why not a single tunnel with better internal segmentation
A reasonable objection here is that WireGuard supports per-peer AllowedIPs, so you could run one tunnel and restrict what each peer is allowed to route to, effectively segmenting privilege inside a single interface instead of splitting into two. That’s a real option, and for some setups it’s the right one. The reason it wasn’t the choice here comes back to what a mistake looks like under each model. With per-peer routing restrictions inside one tunnel, a misconfigured AllowedIPs entry, a copy-paste error while adding a new peer, or a config management bug can silently grant a general-access peer a route into privileged territory, and the failure is invisible until something notices traffic that shouldn’t be possible. With two physically separate tunnels, the equivalent mistake would require putting a peer’s key in the wrong tunnel’s config entirely, which is a much louder, more visible error: the peer either has no way to reach the privileged side at all, or an admin looking at the admin tunnel’s peer list sees an entry that obviously doesn’t belong there. Separation by tunnel turns a subtle routing bug into an obvious misfile, which is a better failure shape even though it costs an extra interface to maintain.
What this doesn’t fix by itself
None of this replaces what happens after the tunnel: a peer being on the admin tunnel is necessary for privileged access, not sufficient. Whatever authenticates and authorizes actions once traffic is inside still has to hold up on its own; the tunnel split narrows who can even attempt privileged actions, it doesn’t stand in for that layer’s own controls. And the design is only as good as key hygiene on each device: WireGuard’s simplicity means there’s no central authority quietly rotating things for you if a private key sits unrotated for a long time, so the discipline of actually removing and rotating peers when a device changes hands or goes away is still a manual habit to keep, not something the topology enforces automatically.
There’s also a bookkeeping cost that’s easy to underrate until it’s actually lived with: two tunnels and per-device peers together mean the real source of truth for “who can reach what” is two separate peer lists, each growing by one entry per device rather than one entry per person. That’s a small, deliberate overhead for a single operator with a handful of devices. It’s the part of this design most likely to need a genuine inventory (which key belongs to which physical device, added on which date, last rotated when) once the device count grows past what fits comfortably in memory. Building that inventory before it’s needed would be premature; not building it once the peer lists get long enough to lose track of by eye would be its own quiet failure mode, just a slower one than a shared tunnel’s.
This tunnel split is one piece of a larger reachability model, not the whole of it — see Six Layers of Reachability for how “does a peer exist” fits alongside routing, firewall rules, DNS, and application auth as one of six independent things that all have to say yes before a request lands anywhere.