Per-application VPN routing — sending only selected apps through an encrypted tunnel while leaving the rest of the host on the normal interface — is an increasingly common requirement for privacy-minded Linux users in 2026. It lets you give a browser or game the protection of your VPN without slowing system-wide services, and it enables fine-grained QoS and DNS controls that a blanket VPN can't provide.
This guide walks through a reliable, pragmatic method that combines network namespaces (netns) for process isolation, WireGuard for the tunnel, nftables for leak prevention, and tc (with optional eBPF classifiers) for per-app QoS. The approach is compatible with mainstream Linux distributions running modern kernels (>= 5.10, recommended 6.x+), systemd, iproute2, WireGuard and tc/bpf toolchains available in 2026.
Why use netns + WireGuard + eBPF?
- Deterministic routing: Network namespaces give you a separate network stack for each app. Unlike process-level firewall tricks, namespaces avoid race conditions and are easy to reason about.
- DNS and leak control: Running a DNS resolver inside the namespace prevents the host resolver from leaking queries to your ISP.
- QoS with precision: tc plus eBPF filters (widely supported in 2026) can classify and prioritize flows initiated by the application without affecting other traffic.
- Compatibility: WireGuard combines speed and simplicity; the same WireGuard interface can carry traffic from multiple namespaces via NAT on the host.
Prerequisites
- A Linux system with kernel 5.10+ (6.x recommended for modern bpf helpers).
- WireGuard (wireguard-tools/wg-quick), iproute2, nftables, tc/iproute2, and bpftool or a bpf framework (aya/libbpf) if you plan to load eBPF programs.
- Root privileges for initial setup.
- Familiarity with ip/netns and basic networking.
Overview of the workflow
- Create a network namespace (appns) and a veth pair that links the namespace to the host.
- Provide the namespace with a private subnet and route its default gateway to the host-side veth.
- Configure a WireGuard interface (wg0) on the host and NAT namespace traffic out through wg0.
- Run the target application inside the namespace.
- Run a DNS resolver (e.g., dnsmasq or dnscrypt-proxy) inside the namespace and pin resolv.conf to 127.0.0.1.
- Use nftables on the host to block any packets from the namespace that try to bypass the WireGuard interface.
- Optional: attach tc + eBPF classifiers on wg0 or the host veth to prioritize/shape the app's traffic.
Step‑by‑step setup
1) Create namespace and veth pair
Pick a namespace name (appns) and an internal subnet (10.200.200.0/24 used here). Run as root or via sudo.
- ip netns add appns
- ip link add veth-host type veth peer name veth-app
- ip link set veth-app netns appns
- ip addr add 10.200.200.1/24 dev veth-host
- ip link set veth-host up
- ip netns exec appns ip addr add 10.200.200.2/24 dev veth-app
- ip netns exec appns ip link set veth-app up
- ip netns exec appns ip link set lo up
- ip netns exec appns ip route add default via 10.200.200.1
The namespace now has a private interface (10.200.200.2) with the host acting as gateway (10.200.200.1).
2) Wire up the WireGuard tunnel on the host
Assume wg0 already configured on the host (via wg-quick or system configuration). All traffic forwarded by the host from the namespace will be NATed out via wg0.
- Verify WireGuard: wg show
- Enable IP forwarding: sysctl -w net.ipv4.ip_forward=1
- Set NAT: nft add table ip nat; nft 'add chain ip nat postrouting { type nat hook postrouting priority 100 ; }'
- nft add rule ip nat postrouting ip saddr 10.200.200.0/24 oifname "wg0" masquerade
Using nftables is preferred to legacy iptables in current distributions. The NAT rule hides namespace addresses behind the WireGuard peer.
3) Run the application inside the namespace
Simple CLI example:
- ip netns exec appns curl https://ifconfig.co
For GUI apps you need to forward display/wayland and possibly Xauthority; run them with the same UID inside the namespace. Example for Firefox (X11):
- export DISPLAY=:0; XAUTHORITY=/home/USER/.Xauthority ip netns exec appns sudo -u USER env DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY /usr/bin/firefox
Tooling such as firejail, podman, or flatpak can also run apps in separate namespaces; this guide uses raw netns for clarity.
4) Fix DNS: run a resolver inside the namespace
The host's resolver might leak queries outside the VPN. Run a lightweight resolver (dnsmasq, unbound, or dnscrypt-proxy) inside the namespace and point /etc/resolv.conf to 127.0.0.1 for the app process.
- Install dnsmasq and configure it to forward to your VPN provider's DNS or a privacy resolver accessible via the tunnel.
- Start dnsmasq in the namespace: ip netns exec appns dnsmasq --no-daemon --listen-address=127.0.0.1 &
- Create resolv.conf for the namespace: ip netns exec appns bash -c 'echo "nameserver 127.0.0.1" > /etc/netns/appns/resolv.conf'
Systemd-resolved-aware systems can instead bind a per-namespace resolver stub. The key is to ensure the app's DNS queries never hit the host resolver or the non-VPN uplink.
5) Prevent leaks: nftables rules to enforce VPN-only egress
Even with NAT, misconfiguration can let packets escape via non-VPN interfaces. Use nftables to drop packets originating from the namespace subnet that do not exit via wg0.
- nft add table ip filter
- nft 'add chain ip filter forward { type filter hook forward priority 0 ; }'
- nft add rule ip filter forward ip saddr 10.200.200.0/24 oifname != "wg0" drop
Optionally add a rule in the host OUTPUT chain to prevent accidental locally-generated packets with those source addresses from escaping other interfaces.
6) Add per‑app QoS: tc + eBPF classifiers (optional but powerful)
For fine-grained prioritization — for example, prioritizing a game's realtime UDP flows over background sync — attach tc qdiscs and classifiers to the host-side veth (veth-host) or the wg0 interface. Modern setups in 2026 often use eBPF programs as tc filters because they can inspect socket metadata and make decisions with minimal overhead.
Example (conceptual):
- Add a root qdisc on wg0: tc qdisc add dev wg0 root handle 1: htb default 20
- Create classes: tc class add dev wg0 parent 1: classid 1:10 htb rate 5mbit; tc class add dev wg0 parent 1: classid 1:20 htb rate 50mbit
- Attach a classifier that marks game traffic: use a small eBPF program that matches dst port 3074/UDP and sets skb->priority or fwmark; then use tc filter flow to direct matches to 1:10
Building eBPF classifiers from source requires libbpf/clang and a small C program. If you prefer not to compile eBPF, classical tc u32 or nftables flowtable matching helps too, though with higher overhead on large flows.
Monitoring: list attached BPF programs with bpftool and inspect statistics with tc -s filter show dev wg0.
Operational tips and examples
- Persisting the setup: turn these steps into a systemd unit that creates netns, starts the namespace DNS, and executes the app as a scope. Ensure units use RuntimeDirectory and proper cleanup on stop.
- Automated cleanup: ip -o netns list and ip netns delete appns when done. For processes left behind, use nsenter or ss to find sockets bound to the namespace.
- Logging and debugging: use ip netns exec appns tcpdump -i veth-app -n to watch traffic from inside the namespace. From the host inspect NAT counters with nft list ruleset and monitor wg show.
- Wayland and GUI apps: if using Wayland, pass the WAYLAND_DISPLAY socket into the namespace (bind-mount or use socat). Flatpak often already uses sandboxing and namespace APIs that can be adapted.
Security considerations
- Running GUIs in a privileged namespace can expose X11/Wayland sockets; ensure Xauthority or Wayland permissions are correct and prefer Wayland for its per-application isolation when possible.
- Firewall rules must be robust: test for DNS and IPv6 leaks with online leak test services from inside the namespace (ip netns exec appns curl https://ipleak.net or equivalent).
- Keep WireGuard keys and DNS resolver configs secure; store keys with filesystem permissions and, if available, a hardware-backed key store.
When to use eBPF vs simple tc/u32 filters
If you need very fast, socket-aware classification (for example matching application PID, cgroup, or socket cookie) use eBPF classifiers attached to tc. eBPF allows you to consult user-space maps and to classify flows with access to richer metadata.
If your classification is port- or IP-based, simpler tc u32 filters or nftables rules are easier to maintain and require no compilation. For many per-app QoS needs, marking flows in the namespace (e.g., using iptables/nftables to set a fwmark per source IP/port) and then shaping on the host is sufficient and more approachable.
Summary
Per-app VPNs on Linux are best built on a small set of well-understood primitives: network namespaces to isolate processes, a host WireGuard interface for an encrypted tunnel, an in-namespace DNS resolver to avoid leaks and nftables rules to enforce egress only through the VPN. For advanced traffic steering and prioritization, attach tc qdiscs and optionally an eBPF classifier to the relevant interface.
This model gives you deterministic, auditable behavior — an application placed into appns will use only the configured tunnel and resolver — and it fits into systemd/service workflows for persistence. As eBPF-based tooling continues to mature in 2026, expect tighter, safer classifiers and more packaged examples; the approach above will remain applicable and portable across popular distributions.
If you want, I can produce a downloadable systemd unit and an example eBPF classifier (C source + build instructions) tuned for a specific game or browser to get you started.