Simple VPN Split Tunnel on Linux
The IP-based split-tunneling that I explore here allows control over which IP addresses go through a default VPN tunnel and which instead go through your regular network interface (or another VPN).
I recently switched over to ProtonVPN and found that the Linux Desktop App does not have split-tunnel options like it does in the Android App.
Luckily it seems pretty easy to configure with the ip
tool and it is compatible with both Wireguard and OpenVPN.
Eventually I plan to use this with Tailscale. ProtonVPN will protect all of my internet traffic, excluding what is sent through my Tailscale Mesh Network. Tailscale handles routing to services that are otherwise not exposed to the public internet, such as in my HomeLab.
The command looks like this:
sudo ip route add $EXCLUDED_ROUTE via $GATEWAY dev $INTERFACE
sudo ip route add 100.64.0.0/10 via 192.168.0.1 dev wlp2s0
Where EXCLUDED_ROUTE
is the CIDR range you want to re-route from the current default (VPN tun0
) to somewhere else. The example above uses Tailscales IPv4 range as listed here.
INTERFACE
and GATEWAY
are the new target network. In my case I use my devices WiFi interface wlp2s0
that routes through my networks 192.168.0.1
gateway router.
I verified the above command by opening several 'What's my IP' webpages and found their IP addresses in my Browsers Network tab. By using the route add
command with those Webpage IPs and refreshing the page they would now show my unmasked IP address. Refreshing other pages whose IP addresses I have not exclude still correctly show the VPN Source IP.
These route changes do not persist between reboots. If you want them to be run automatically then add them to a startup script or wrap them in a service.
Finally, this script makes it easier to find your gateway and interface.
#!/bin/bash
# Usage: ./add-route-exclusion.sh 100.64.0.0/100
EXCLUDED_ROUTE=$1
# Validate CIDR notation address for user input
if [[ ! "$EXCLUDED_ROUTE" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ ]]; then
echo "Error: Not a valid CIDR notation address."
echo "Usage: $0 <CIDR_notation_address>"
exit 1
fi
# Get our default route (excluding the VPN tunnel tun0)
ROUTE=$(ip route | grep default | grep -v tun0 | head -n1)
GATEWAY=$($ROUTE | awk '{print $3}')
INTERFACE=$($ROUTE | awk '{print $5}')
# e.g. ip route add 100.64.0.0/10 via 192.168.0.1 dev wlp2s0
ip route add $EXCLUDED_ROUTE via $GATEWAY dev $INTERFACE
# Remove the route with this
#ip route del $EXCLUDED_ROUTE via $GATEWAY dev $INTERFACE