Start the VPN inside its own namespace, run one application there, and traffic still leaves by the host route. Nothing is broken: the namespace only governs processes that enter it, and the leak came from one that never did. Interfaces, addresses, routes, firewall rules and sockets are what the namespace owns. Files, accounts and already-running services are not, which is where most of the surprises come from.

What the namespace owns, and where the socket is born

Move an interface into a namespace and the host loses it; build a tunnel inside and the host cannot see it. That is the mechanism behind a namespace-scoped VPN: the device and the routes pointing at it live in a context only the chosen processes can reach. The host keeps its default route, so everything outside continues untouched.

Creation context matters as much as current location for WireGuard: the UDP socket is bound in the namespace where the interface was created, even after the device moves elsewhere. Which routing table the encrypted packets follow depends on that birthplace. When the socket stays in a context with no route to the peer, the interface looks up and the handshake never leaves. Check its birthplace, then its current location.

What a namespace does not isolate

Nothing here separates filesystems, accounts or secrets, and none of those boundaries appear because you created a namespace. Resolver configuration is a file, so a process inside can still read a setting that points at the host resolver and send its query out through whatever route that implies. An application that hands the request to an already-running broker or system service involves a process in a different context entirely. Both are requests the boundary was never meant to answer.

The fix is a per-namespace file at /etc/netns/<name>/resolv.conf, or a bind mount of a prepared file, pointed at a resolver reachable from inside. Editing /etc/resolv.conf on the host changes nothing that the namespace sees.

Process placement is the control

The isolation you get is exactly the isolation of the processes you start inside. A shell started through the namespace exec helper, and everything it spawns, stays in; a desktop application launched from the host session does not. Create the context, move the device, then prove both sides:

ip netns add vpn1
ip link set wg0 netns vpn1
ip netns exec vpn1 wg show
wg show

Read the first wg show from inside the namespace and the second from the host, and expect only one of them to list the peer. If both do, the device never moved; if neither does, it went somewhere else. When the launcher is a service manager, the unit file is what to edit, not the desktop shortcut. A unit without the namespace setting starts the daemon in the host context and quietly undoes the design.

Test from the application context

Run the check with the same binary and the same user the application uses, inside the namespace. Include DNS and both address families, because an IPv6 route you never considered is a common way out. A test from your own shell only proves that your shell is isolated.

Then run the identical check on the host and confirm the answers differ: tunnel address and intended exit inside, host route and office exit outside, and the resolver by name. Name namespaces after the deployment rather than the protocol, so a cleanup script cannot confuse two of them.

Forwarding, failure policy and cleanup

For a gateway, turn forwarding on deliberately and decide what should happen when the tunnel drops. A namespace does not block routes you install inside it, so a fallback route to the host will be used if you create one. That is a policy decision, and it should be written down before an outage tests it.

Record the namespace names, the interfaces and who owns the configuration, so cleanup removes only what the deployment created. Resetting host networking during a rollback turns a cleanup into an outage. The network_namespaces(7) page is short and worth reading before you design that path.

Ngaahi tokoni fekau'aki