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.

KeyValue
Server public IP203.0.113.10
Client VPN IP10.0.0.2
Server VPN IP10.0.0.1
WireGuard port51820
DNS server1.1.1.1
Local LAN subnet192.168.1.0/24
Local gateway192.168.1.1
NAS IP (example)192.168.1.100
Config path/etc/wireguard/wg0.conf
OSUbuntu 24.04 LTS

0. Prerequisites


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:

  1. It creates a custom routing table (e.g., table 51820) with a default route through wg0
  2. It adds a rule: packets without WireGuard’s firewall mark use the custom table
  3. It adds a rule: suppress_prefixlength 0 on 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 /24 route for your LAN (added automatically when your network interface gets an IP). A /24 prefix 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 through wg0 to the VPN.

Note: You do not need PostUp hacks to delete routes, and you do not need to list private ranges in AllowedIPs. The policy routing handles the split automatically.

Note: This behavior applies to standalone wg-quick on Ubuntu. If your system manages connections through NetworkManager, a custom routing daemon, or policy engines, route handling may differ. The commands in this tutorial assume wg-quick controls 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 local
32764: from all lookup main suppress_prefixlength 0
32765: not from all fwmark 0xca6c lookup 51820
32766: from all lookup main
32767: 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/32
DNS = 1.1.1.1
PostUp = ip route add 192.168.2.0/24 via 192.168.1.1 dev eth0
PostDown = 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:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 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 eth0
PostDown = 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/32
DNS = 1.1.1.1
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = 203.0.113.10:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 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 VPN
  • ping 192.168.1.100 — confirm local devices are reachable
  • ip rule show — inspect policy routing rules
  • ip 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.

Leave a Reply