A route into a VPN network carries two addresses that are easy to confuse: the destination prefix the packet is trying to reach and the next hop that will move it one step closer. On a LAN host, that next hop has to be a router you can actually reach on the same link. The remote VPN address is not a next hop, however neatly it matches the destination you have in mind.

This runbook covers the ordinary case: a laptop on 192.168.20.0/24, a VPN gateway with a LAN address of 192.168.20.5, and a remote network of 10.70.0.0/24 behind that gateway. Every command below assumes you are on the laptop unless it says otherwise.

Two addresses, one route

The intended entry is 10.70.0.0/24 via 192.168.20.5. The prefix says where the packet is going; the via address says who carries it there. The kernel refuses to install a route whose gateway it cannot reach through the interface you name, and that refusal is the whole point: a next hop that needs the tunnel to be reachable cannot be the next hop that builds the tunnel.

So the first check is not "is the gateway up" but "is the gateway on this link". The laptop's own subnet, its ARP or neighbour entry, and the interface in the route all have to agree. An address that exists only inside 10.70.0.0/24 can be the destination of a second route, never the via of the first.

What the next hop commits the gateway to

Installing the route on the laptop only decides the first handoff. The gateway then needs its own path: forwarding enabled, a tunnel route for 10.70.0.0/24, and any access policy that governs who may use it. On Linux that is net.ipv4.ip_forward=1 plus a tunnel route; a firewall in front of it needs a rule that permits the forwarded flow.

The return direction is the part people forget. The remote side must have a route back to 192.168.20.0/24, or the design must translate the laptop's source into something the remote side knows. A working ping from the gateway proves the gateway can reach the remote network; it says nothing about whether the laptop's packets survive the trip back.

Verify the lookup from the host that sends

Ask the kernel which next hop it would choose, and read the interface in the answer:

ip route get 10.70.0.10
ip route show 10.70.0.0/24
ip neigh show 192.168.20.5

If the lookup reports the gateway and the LAN interface, the first leg is configured as intended. If it reports the tunnel device, something earlier in the table is more specific and is winning. If the route will not install at all, the kernel is telling you the gateway is unreachable, and that message is more useful than any packet capture.

Then repeat the test from the right place. A request created on the gateway uses the gateway's own source address, which can hide a missing return route or a policy that only permits certain sources. The ip-route manual separates destination, gateway and the onlink override, and the override is worth understanding before you use it.

Do not use onlink to fake reachability

onlink tells the kernel to skip the reachability check for a gateway and send to it anyway. It does not create a path, resolve ARP for an address that is not there, or make a remote router answer on your Ethernet segment. It is legitimate for gateways that answer but do not sit in a connected subnet, and it is a trap when used to silence an error you have not explained.

If the route only installs with onlink, the next question is why the gateway is not where you think it is. A wrong VLAN, a second address on the wrong interface, or a typo in the last octet all produce this, and each of them also breaks the return path once traffic starts flowing.

Point-to-point tunnels are not Ethernet

A tunnel device does not do ARP and often has no meaningful subnet of neighbours. Its routes frequently look like 10.70.0.0/24 dev tun0, with no via at all, because the peer is the only other endpoint. Copying a LAN-style via into that table produces a route the kernel may reject or, worse, one that points at an address the peer never answers.

Keep the two models separate in the runbook: a LAN route has a reachable gateway, a tunnel route has an interface and a peer. WireGuard, OpenVPN and IPsec all expose slightly different shapes, so record the shape you actually used instead of generalising from one host.

Add, confirm, roll back

The whole change is three commands and one check, and it should be written down in that order:

sudo ip route add 10.70.0.0/24 via 192.168.20.5
ip route get 10.70.0.10
ping -c 3 10.70.0.10
sudo ip route del 10.70.0.0/24 via 192.168.20.5

Confirm the path, confirm one destination, then keep the delete line ready in case the change makes the host unreachable in a way you did not predict. If the route must survive a reboot, put it in the host's network configuration rather than a shell profile, and test the reboot before you rely on it. A route that only exists in the shell that added it is a route you will debug twice.

Nuŋlɔɖi siwo ku ɖe eŋu