Add Routes on Linux via .sh Script
NetRoute Pro generates a plain Bash script with ip route add commands. Apply it with one command for instant routes. Persistence requires one extra step depending on your distro.
Command Syntax
Syntax (idempotent — use this in scripts):
ip route replace <CIDR> via <GATEWAY> dev <VPN_IF>
Example:
ip route replace 1.1.1.0/24 via 10.0.0.1 dev wg0
Run as root or with sudo. NetRoute Pro generates these as a .sh script — apply with bash routes.sh. replace creates the route if missing and updates it if present, so re-running is safe; add would fail with RTNETLINK answers: File exists on a duplicate and (combined with set -e) abort the script.
Prerequisites
iproute2(preinstalled on virtually all modern distros — Ubuntu, Debian, Fedora, Arch, etc.)- Root/
sudoaccess - A configured VPN interface (WireGuard, OpenVPN, IKEv2, etc.) that is already up
- NetRoute Pro Chrome extension installed
Step 1. Generate .sh in NetRoute Pro
- Open the target website in Chrome
- Click the NetRoute Pro icon in your extensions
- Select the Linux platform
- Set the gateway to your VPN interface IP (for example
10.8.0.1) - Choose aggregation mask (recommended
/24) - Click Analyze Website
- Download the result as
routes.sh
Step 2. Apply the script
chmod +x routes.sh
sudo bash routes.sh
Routes are applied immediately — but only until the next reboot or until your VPN interface goes down. For permanence, see Step 3.
Step 3. Make routes persistent
Pick one of the three options below, depending on how your system manages networking.
systemd-networkd
If your distro uses systemd-networkd (default on many minimal installs, Arch, server distros), create a drop-in file:
sudo mkdir -p /etc/systemd/network/10-vpn.network.d
sudo nano /etc/systemd/network/10-vpn.network.d/routes.conf
Add one [Route] block per subnet:
[Route]
Destination=104.21.32.0/24
Gateway=10.8.0.1
[Route]
Destination=172.67.182.0/24
Gateway=10.8.0.1
Apply:
sudo systemctl restart systemd-networkd
NetworkManager dispatcher
If you use NetworkManager (Ubuntu desktop, Fedora Workstation, most laptops), save a dispatcher hook:
sudo nano /etc/NetworkManager/dispatcher.d/99-vpn-routes
Example content — checks that the triggering interface is your VPN device and re-runs the ip route add commands:
#!/bin/bash
# $1 = interface name, $2 = action
if [ "$1" = "<your-vpn-interface>" ] && [ "$2" = "up" ]; then
ip route add 104.21.32.0/24 via 10.8.0.1
ip route add 172.67.182.0/24 via 10.8.0.1
fi
Make it executable:
sudo chmod +x /etc/NetworkManager/dispatcher.d/99-vpn-routes
systemd oneshot service (recommended)
Works on any distro with systemd (Ubuntu, Debian, Fedora, Arch, RHEL, openSUSE — virtually all modern distros). Properly waits for the VPN tunnel to be up before applying routes — no race conditions, no sleep hacks.
sudo mv routes.sh /usr/local/bin/routes.sh
sudo chmod +x /usr/local/bin/routes.sh
sudo tee /etc/systemd/system/vpn-routes.service > /dev/null <<'EOF'
[Unit]
Description=Apply VPN split-tunnel routes
After=network-online.target wg-quick@wg0.service
Wants=network-online.target
Requisite=wg-quick@wg0.service
BindsTo=wg-quick@wg0.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/routes.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now vpn-routes.service
Replace wg-quick@wg0.service with your actual VPN unit:
- OpenVPN:
openvpn-client@<name>.service - strongSwan / IPsec:
strongswan.service - Generic (no VPN-specific dependency): drop the
Requisite=andBindsTo=lines, keep onlyAfter=network-online.target
Verify the service:
systemctl status vpn-routes.service
journalctl -u vpn-routes.service
@reboot cron? The cron daemon usually starts before the VPN tunnel is up — ip route add runs against an interface that doesn’t exist yet, fails silently, and you discover broken routes only after a reboot. systemd’s After= + Requisite= waits for the VPN unit and eliminates the race. BindsTo= also stops the routes when the VPN goes down.
DNS leak — required reading
This guide routes traffic by IP. It does not route DNS. Your browser still asks the system resolver (usually your ISP’s, learned via DHCP) for example.com first — only the resulting IP traffic is encrypted through the VPN. The ISP sees which sites you visit even though the data is encrypted.
Three options, by threat model:
- Hide DNS from ISP fully (split-DNS). On systemd-resolved, route DNS for tunneled domains through the VPN’s resolver only:
Nowsudo resolvectl dns wg0 10.0.0.1 sudo resolvectl domain wg0 ~example.com ~another-site.comexample.comqueries go through the VPN, everything else uses the default resolver. Replace10.0.0.1with your VPN provider’s internal DNS. - Reduce ISP visibility (public DoH/DoT). Point the system resolver at a public encrypted resolver:
ISP no longer sees domain queries; the public resolver does.sudo resolvectl dns wg0 1.1.1.1#cloudflare-dns.com sudo resolvectl dnsovertls wg0 yes - Accept the leak. If your goal is content access, not surveillance avoidance, this is fine — the data path is still encrypted, only the lookup metadata leaks.
Verify the current state with dnsleaktest.com or browserleaks.com/dns — the resolver shown should belong to your VPN or chosen public DoH provider, not your ISP. Locally, resolvectl status shows the per-link DNS configuration.
IPv6 dual-stack bypass
If the destination has an AAAA record (most popular sites do — Cloudflare, Google, AWS are all dual-stack), the OS prefers IPv6 over IPv4 (RFC 6724). Routes added via this guide are IPv4-only — IPv6 traffic skips the VPN entirely and exits via your ISP’s default v6 route. Result: VPN looks active but does nothing for dual-stack destinations.
Quick check from the same machine after the VPN is up:
curl -6 -s -o /dev/null -w "remote=%{remote_ip}\n" https://example.com
ip -6 route get $(dig +short AAAA example.com | head -1)
If the IPv6 trace doesn’t go through your VPN interface, you’re bypassing the tunnel. Two fixes:
- Route v6 through the VPN too (if your provider has an IPv6 endpoint). Generate IPv6 routes in NetRoute Pro — same flow, different prefixes (e.g.
2606:4700::/32). - Disable IPv6 system-wide:
All traffic falls back to v4 and gets tunneled correctly.sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1 sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1 # Persistent: echo the same lines into /etc/sysctl.d/99-disable-ipv6.conf
Fail-closed (kill switch)
When the VPN tunnel goes down (network change, provider hiccup, sleep/wake), the kernel removes any routes bound to the dead interface — and traffic to previously-tunneled CIDRs falls back to your ISP’s default route. Result: silent leak with no error visible to the user. To enforce fail-closed (block instead of leak):
# nftables (modern Linux)
sudo nft add table inet vpnkill
sudo nft add chain inet vpnkill output { type filter hook output priority 0 \; }
sudo nft add rule inet vpnkill output ip daddr 1.1.1.0/24 oif != wg0 drop
sudo nft add rule inet vpnkill output ip daddr 8.8.8.0/24 oif != wg0 drop
# Repeat for each tunneled CIDR.
iptables equivalent:
sudo iptables -I OUTPUT ! -o wg0 -d 1.1.1.0/24 -j REJECT
sudo iptables -I OUTPUT ! -o wg0 -d 8.8.8.0/24 -j REJECT
Now traffic to those prefixes via any interface other than wg0 is dropped — VPN down = blocked, not leaked. The user sees a connection error (loud signal that VPN is down) instead of silently exiting via the ISP. This is the safer failure mode.
Verify
Check that the routes are in the kernel table:
ip route show | grep 10.8.0.1
All added subnets should appear, bound to your VPN gateway.
Rollback
The script saves a snapshot of your route table to /tmp/route-table.<timestamp>.txt before applying changes — useful for diagnosing what changed. To remove the routes added by NetRoute Pro, delete each prefix:
for cidr in 1.1.1.0/24 8.8.8.0/24 162.159.0.0/16; do
sudo ip route del "$cidr" 2>/dev/null || true
done
Or, more conservatively, generate a reverse list from your routes.sh — replacing replace (or add) with del:
awk '/ip route /{ sub(/^ip route (replace|add)/, "ip route del"); print }' routes.sh | sudo bash
If something went catastrophically wrong, the snapshot at /tmp/route-table.<timestamp>.txt shows the pre-change state — you can manually re-add anything you needed.
Common issues
Network is unreachable
Your VPN interface is down. Check with:
ip link show
Bring the interface up before running the script.
Permission denied
ip route replace needs root. Always invoke with sudo.
Example Configuration File
Ready-to-edit template with inline comments. Replace the example routes with output from NetRoute Pro for your target sites.
linux-routes.sh— Bash script withip route addcommands
#!/bin/bash
# Example Linux routing script for split tunneling.
# Generated by NetRoute Pro: https://alexander2k.github.io/netroute-site/
#
# Run as root or with sudo. Routes added here last until reboot —
# for persistence see the Linux guide (systemd-networkd / NetworkManager).
#
# Idempotent: uses `ip route replace` so re-running the script is safe.
# `add` would fail with "RTNETLINK answers: File exists" on a duplicate;
# combined with `set -e`, the first existing route would abort the script
# and skip the rest. `replace` creates if missing, updates if present.
set -euo pipefail
DEV="wg0" # VPN interface name (wg0, tun0, ppp0, ...)
GW="10.0.0.1" # VPN gateway IP (or use only "dev $DEV" if VPN is point-to-point)
# Snapshot current route table — useful for rollback if something breaks.
SNAPSHOT="/tmp/route-table.$(date +%s).txt"
ip route show > "$SNAPSHOT"
echo "Route table snapshot saved to $SNAPSHOT"
ip route replace 1.1.1.0/24 via "$GW" dev "$DEV"
ip route replace 8.8.8.0/24 via "$GW" dev "$DEV"
ip route replace 162.159.0.0/16 via "$GW" dev "$DEV"
# Verify with: ip route show
# Remove a single route: ip route del <CIDR>
# Full rollback (delete all routes added by this script):
# for cidr in 1.1.1.0/24 8.8.8.0/24 162.159.0.0/16; do
# ip route del "$cidr" 2>/dev/null || true
# done
Tip: Need a config without these comment lines? In NetRoute Pro options, uncheck “Include comments in exported files” — the extension will export only the route commands. Useful for routers that don’t tolerate comment lines.
View all example configs on GitHub →
Official Documentation
Related Guides
- Keenetic — Routes from
.batfile upload - MikroTik — RouterOS
.rscscript import - WireGuard — split tunneling with
AllowedIPs - OpenVPN — client config
routedirectives
Ready to try?
NetRoute Pro — a free Chrome extension to generate routes from any website.
Install Extension