Ping 10.20.0.5 through the tunnel and nothing comes back, while the route table insists that subnet is reachable. Routing and IPsec answer different questions, and only one of them is about reachability. The forward lookup picks a path, then the security policy database decides whether that path must be protected, left alone or refused. When the two disagree, the packet dies quietly, so read them in that order.

Start with the lookup, not the table

Ask the kernel what it would really do: ip route get 10.20.0.5 prints the chosen route, the outgoing interface and the source address it would use. A prefix, a policy rule, a mark or a preferred source can all change that answer, which is why the live lookup beats reading the table by eye.

Test a destination inside the protected range, not the gateway address. Reaching the far-side gateway only proves that some route exists; it says nothing about the subnet you actually need. Then confirm the interface the lookup selected is the one you expect the tunnel to use.

Match the selectors on both sides

The policy database holds its own entries, and each one names a direction, a source, a destination and a protocol, then states what should happen to matching packets. Use ip xfrm policy to list them; the flags show whether the action is protect, bypass or block. A policy whose selectors are wider or narrower than your test flow will not match it, and the packet leaves unprotected. That is the failure you are hunting, not a missing route.

Compare the actual values: source, destination, protocol and ports. If the lookup resolved one address pair while the policy entry covers a different one, the two views describe different traffic. Re-run both with the same tuple before drawing a conclusion, or you will edit a policy that was never in the path.

A policy match is not installed state

A matching policy only says the packet must be protected; the kernel still needs a security association to do the protecting. List them with ip xfrm state, or with swanctl --list-sas when strongSwan does the negotiating, and read the selectors and counters on each entry.

When a policy matches and no association covers the same selectors, the packet is dropped rather than sent in clear text. That is the drop to look for when the tunnel is up but a single subnet fails. Counters tell the rest: an association that never increments was never selected, while one that increments and still ends in a drop points at the remote side. Fixing selectors and rekeying are different repairs, so decide which one you need before touching the config.

What a route-based tunnel still requires

An XFRM interface makes the tunnel look like a device you can route into, which is genuinely useful. It does not remove the policy and state that decide what happens once the packet arrives there. The route contributes a next hop and nothing more.

If the device exists and the route points into it while no association matches the inner selectors, the packet is discarded, and no amount of route editing produces keys or selectors. Check the interface with the same source and destination you routed, then compare that with the state list.

Settle ownership before editing anything

A host firewall adds a third decision that neither database knows about. A correct route plus a matching policy plus an installed association can still end at a dropped packet, and the application reports only the timeout. Confirm the firewall allows the protected traffic before you blame IPsec. Capturing on the tunnel device and on the physical interface shows which side of the encryption the packet disappeared.

Configuration ownership decides whether your fix survives a restart. One component may install state while a different one manages routes, and a hand edit to the second is overwritten the next time the tunnel comes up. Change the owning configuration, then re-read both views; RFC 4301 keeps the two databases distinct when you have to explain the split to someone else.

مۇناسىۋەتلىك ماقالىلەر