WireGuard Split Tunneling: Route Internet Through VPN, Keep Local Access
Summary: Configure WireGuard to route all internet traffic through the VPN while keeping direct access to local network devices like NAS drives and printers.
| Key | Value |
|---|---|
| Server public IP | 203.0.113.10 |
| Client VPN IP | 10.0.0.2 |
| Server VPN IP | 10.0.0.1 |
| WireGuard port | 51820 |
| DNS server | 1.1.1.1 |
| Local LAN subnet | 192.168.1.0/24 |
| Local gateway | 192.168.1.1 |
| NAS IP (example) | 192.168.1.100 |
| Config path | /etc/wireguard/wg0.conf |
| OS | Ubuntu 24.04 LTS |
0. Prerequisites
- A working WireGuard client connection (see Tutorial 01: WireGuard Client Setup)
- Local network devices you want to reach while the VPN is active (e.g., a NAS at
192.168.1.100) sudoor root access- Familiarity with CIDR notation and routing basics (see Tutorial 00: Networking Basics for a refresher)
1. What Is Split Tunneling?
Split tunneling means routing some traffic through the VPN and some traffic outside it. In this tutorial, the split is:
- Internet traffic — routed through the VPN tunnel (encrypted, exits via the VPN server)
- Local LAN traffic — sent directly over your local network (not encrypted by the VPN, stays on your LAN)
This gives you VPN privacy for internet browsing while keeping access to local resources like NAS drives, printers, and home automation devices.
2. How wg-quick Handles This Automatically
If you followed Tutorial 01 and set AllowedIPs = 0.0.0.0/0, split tunneling to your local network already works. Here is why.
When wg-quick sees AllowedIPs = 0.0.0.0/0, it does not simply replace your default route. Instead, it sets up policy-based routing:
- It creates a custom routing table (e.g., table
51820) with a default route throughwg0 - It adds a rule: packets without WireGuard’s firewall mark use the custom table
- It adds a rule:
suppress_prefixlength 0on the main table — this suppresses only the default route (prefix length/0) in the main table
The result is that the kernel evaluates routes like this:
- Traffic to
192.168.1.100— the main table has a specific/24route for your LAN (added automatically when your network interface gets an IP). A/24prefix covers 256 addresses and has prefix length 24, so it is not suppressed. Traffic goes directly to your LAN. - Traffic to
8.8.8.8— the main table’s default route (0.0.0.0/0, prefix length/0— meaning “all addresses”) is suppressed. The kernel falls through to the custom table, which routes it throughwg0to the VPN.
Note: You do not need
PostUphacks to delete routes, and you do not need to list private ranges inAllowedIPs. The policy routing handles the split automatically.
Note: This behavior applies to standalone
wg-quickon Ubuntu. If your system manages connections through NetworkManager, a custom routing daemon, or policy engines, route handling may differ. The commands in this tutorial assumewg-quickcontrols the WireGuard interface directly.
3. Verify Split Tunneling Is Working
Bring up the tunnel if it is not already running:
sudo wg-quick up wg0
3.1 Check Internet Routes Through the VPN
curl ifconfig.me
The output should show the VPN server’s IP (203.0.113.10), not your real public IP.
3.2 Check Local Devices Are Reachable
ping -c 3 192.168.1.100
You should get replies from your NAS (or other local device) without the traffic going through the VPN tunnel.
3.3 Inspect the Routing Rules
View the policy routing rules that wg-quick created:
ip rule show
You should see entries like:
0: from all lookup local32764: from all lookup main suppress_prefixlength 032765: not from all fwmark 0xca6c lookup 5182032766: from all lookup main32767: from all lookup default
The suppress_prefixlength 0 line is the key — it tells the kernel to ignore the main table’s default route, forcing internet traffic into table 51820 (which routes through wg0).
View the custom routing table:
ip route show table 51820
You should see a default route through wg0:
default dev wg0 scope link
View the main table to confirm your LAN route is still there:
ip route show table main
Look for a line like:
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.50
This is the route that keeps local traffic on your LAN.
4. When You Need Manual Route Adjustments
The automatic split works for devices on your directly connected subnet. You may need manual adjustments in two cases.
4.1 Accessing Other Local Subnets
If your network has multiple subnets (e.g., VLANs) and you need to reach devices on a subnet you are not directly connected to, add a static route in PostUp:
[Interface]PrivateKey = <CLIENT_PRIVATE_KEY>Address = 10.0.0.2/32DNS = 1.1.1.1PostUp = ip route add 192.168.2.0/24 via 192.168.1.1 dev eth0PostDown = ip route del 192.168.2.0/24 via 192.168.1.1 dev eth0[Peer]PublicKey = <SERVER_PUBLIC_KEY>Endpoint = 203.0.113.10:51820AllowedIPs = 0.0.0.0/0PersistentKeepalive = 25
The PostUp line adds a route for the 192.168.2.0/24 subnet through your local gateway (192.168.1.1). The PostDown line removes it when the tunnel goes down.
4.2 Excluding Specific Public IPs from the VPN
If you need a specific public IP (e.g., a work server at 198.51.100.20) to bypass the VPN:
PostUp = ip route add 198.51.100.20/32 via 192.168.1.1 dev eth0PostDown = ip route del 198.51.100.20/32 via 192.168.1.1 dev eth0
Warning: Any traffic you exclude from the VPN is sent unencrypted over your regular internet connection. Only exclude addresses you trust.
5. Common Mistakes to Avoid
Adding private ranges to AllowedIPs:
AllowedIPs = 0.0.0.0/0, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
This is redundant. 0.0.0.0/0 already covers every address. The extra ranges have no effect and add visual clutter.
Deleting routes in PostUp:
PostUp = ip route del 192.168.0.0/24 dev wg0
With wg-quick‘s policy routing, there is no route for 192.168.0.0/24 on wg0 to delete. This command either fails silently or does nothing useful.
Setting Table = off:
Table = off
This disables all automatic route creation from AllowedIPs. wg-quick will not add any routes — no policy routing, no default route, nothing. You are responsible for adding routes manually (for example, in PostUp scripts). Unless you have a specific reason to manage routing yourself, avoid Table = off and let wg-quick handle it.
6. Full Working Configuration
This is the complete wg0.conf for a split-tunneling client. No special routing hacks are needed:
[Interface]PrivateKey = <CLIENT_PRIVATE_KEY>Address = 10.0.0.2/32DNS = 1.1.1.1[Peer]PublicKey = <SERVER_PUBLIC_KEY>Endpoint = 203.0.113.10:51820AllowedIPs = 0.0.0.0/0PersistentKeepalive = 25
Tip: This is identical to the basic client config from Tutorial 01. Split tunneling for local devices is the default behavior — not something you need to add.
Summary
Split tunneling with WireGuard on Ubuntu works automatically when you use AllowedIPs = 0.0.0.0/0 with wg-quick. The policy-based routing ensures:
- Internet traffic goes through the VPN tunnel
- Local LAN traffic stays on your local network
No PostUp hacks are needed for the common case. Manual route adjustments are only required for multi-subnet networks or specific public IP exclusions.
Key commands for verification:
curl ifconfig.me— confirm internet exits via the VPNping 192.168.1.100— confirm local devices are reachableip rule show— inspect policy routing rulesip route show table 51820— view the VPN routing table
The next tutorial covers a kill switch to block all traffic if the VPN connection drops.
