Why I Set Up Split Tunneling with WireGuard
I run WireGuard on my home network to access internal services when I'm away—things like my Proxmox dashboard, Synology NAS, and n8n workflows. But I didn't want all my traffic going through the tunnel. Streaming services block VPN traffic, local network printers become unreachable, and routing everything through home adds unnecessary latency.
I needed selective routing: send only specific traffic through WireGuard and let everything else go direct. That's split tunneling, and WireGuard handles it through route configuration, not application-level filtering.
My Setup
I'm running WireGuard on a Proxmox LXC container (Debian 12). The server listens on port 51820 and assigns client IPs from the 10.8.0.0/24 range. My clients are:
- Windows 11 laptop
- Android phone (Pixel 7)
- MacBook Pro (M1, macOS Sonoma)
Each client has the official WireGuard app installed. I'm not using a commercial VPN provider—this is entirely self-hosted.
How I Configured the Server
My server config (/etc/wireguard/wg0.conf) looks like this:
[Interface] PrivateKey = [server_private_key] Address = 10.8.0.1/24 ListenPort = 51820 PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE [Peer] PublicKey = [client_public_key] AllowedIPs = 10.8.0.2/32
The PostUp/PostDown rules enable IP forwarding and NAT. Without them, clients can reach the server but nothing beyond it. I had to enable IP forwarding in the kernel too:
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf sysctl -p
I didn't use ufw or firewalld—just raw iptables because I wanted direct control over what gets forwarded.
Client Configuration for Split Tunneling
The key to split tunneling is the AllowedIPs field in the client config. By default, if you set it to 0.0.0.0/0, all traffic routes through WireGuard. That's full tunneling.
For split tunneling, I only list the networks I want to reach through the VPN:
[Interface] PrivateKey = [client_private_key] Address = 10.8.0.2/32 DNS = 10.8.0.1 [Peer] PublicKey = [server_public_key] Endpoint = [my_home_public_ip]:51820 AllowedIPs = 10.8.0.0/24, 192.168.1.0/24 PersistentKeepalive = 25
This tells the client: "Route traffic destined for 10.8.0.0/24 (WireGuard network) and 192.168.1.0/24 (my home LAN) through the tunnel. Everything else goes direct."
The DNS field points to my Pi-hole running on the Proxmox host. This way, DNS queries for internal services resolve correctly even when I'm remote.
What Worked on Each Platform
Windows
The WireGuard Windows client respects AllowedIPs exactly as configured. I didn't need PostUp/PostDown scripts or registry edits. Some guides suggest adding Table = off or route manipulation commands, but I never needed them.
One issue: Windows DNS resolution sometimes leaked to my ISP even with DNS set in the config. I fixed this by setting the WireGuard interface's DNS priority higher in the adapter settings. Open Network Connections, right-click the WireGuard adapter, Properties → Internet Protocol Version 4 → Advanced → DNS tab → uncheck "Register this connection's addresses in DNS."
Android
The Android app (official WireGuard from F-Droid) handles split tunneling cleanly. I use the same config as Windows. The app also has an "Exclude applications" feature, but I don't use it—I prefer routing by destination network, not by app.
Battery drain was noticeable at first. I set PersistentKeepalive to 25 seconds, which keeps the tunnel alive through NAT but doesn't hammer the battery. Without it, the connection would drop after a few minutes of inactivity.
macOS
Same config works on macOS. The official WireGuard app installs cleanly via Homebrew:
brew install --cask wireguard-tools
I import the config file through the GUI. One quirk: macOS sometimes routes IPv6 traffic outside the tunnel even when I don't specify ::/0 in AllowedIPs. I disabled IPv6 on the WireGuard interface to avoid leaks:
sudo networksetup -setv6off "WireGuard"
This isn't ideal if you actually use IPv6, but I don't on my home network yet.
What Didn't Work
I tried using PostUp/PostDown scripts on the client side to manipulate routing tables directly. This broke more than it fixed. WireGuard already handles routing via AllowedIPs—adding manual route commands just caused conflicts.
I also tried routing all traffic (0.0.0.0/0) and then excluding specific networks with ip rule commands. This works in theory but requires maintaining exceptions for every local network, streaming service, and CDN. It's fragile and breaks when networks change.
Using a proxy client like Proxifier (mentioned in some guides) is unnecessary if you configure AllowedIPs correctly. I never needed it.
Testing Split Tunneling
To verify split tunneling works, I check routes and test connectivity:
# On Linux/macOS ip route show ping 192.168.1.1 # Should go through tunnel curl ifconfig.me # Should show my public IP, not home IP # On Windows route print ping 192.168.1.1 curl ifconfig.me
If split tunneling is working, pinging my home LAN (192.168.1.1) should succeed, but checking my public IP should show my current location's IP, not my home IP.
I also check DNS resolution:
nslookup proxmox.local
This should resolve to my Proxmox host's internal IP (192.168.1.100) even when I'm remote, because DNS queries go through the tunnel to Pi-hole.
Limitations I Hit
Split tunneling by network range means I can't selectively route traffic from specific apps. If an app needs to reach both internal and external resources, it's all or nothing based on destination IP.
Some services (like Microsoft Teams) use dynamic IP ranges that change frequently. I can't reliably route them through WireGuard without routing entire CDN blocks, which defeats the purpose of split tunneling.
Mobile data on Android sometimes doesn't play well with WireGuard's persistent connections. The tunnel stays up, but traffic stalls until I toggle the connection off and on. I haven't solved this—it's intermittent and hard to debug.
Key Takeaways
- Split tunneling in WireGuard is configured via AllowedIPs on the client side, not the server.
- List only the networks you want routed through the tunnel. Everything else goes direct.
- DNS leaks are a real issue—set DNS in the client config and verify it's being used.
- Avoid manual route manipulation unless you have a specific reason. WireGuard handles it.
- Test with ping and curl to confirm traffic is routing as expected.
This setup has been stable for me for over a year. I can access my home services from anywhere without routing all my traffic through a single point.