Two independent decisions happen to the same packet, and either one can end the request silently. The routing lookup picks an interface and a next hop, then the packet filter decides whether that combination is allowed through. A timeout tells you one of the two refused, not which. Work the stages in order and the repair usually stops being a guess.

Two decisions, one packet, two owners

The routing lookup consults policy rules first, then the table they point at, and picks the most specific prefix that matches the destination. A client that installed a tunnel route owns that decision, and nothing about a firewall rule can substitute for it. When the lookup points somewhere unexpected, every later stage is working on the wrong packet.

Filtering is separate. On a Linux host the netfilter hooks split traffic by who the packet is for: output leaves the machine, input terminates on it, forward passes through it. A gateway that accepts VPN clients and routes them to a LAN needs the forward path, which is exactly the chain most default configurations leave empty.

Source selection belongs to the same decision, and it is where most mismatches hide. The kernel picks a source address from the outgoing interface unless the route names one, so a filter rule written for the LAN subnet never matches a packet leaving with the tunnel address.

Read what the lookup actually chose

Print the decision instead of inferring it. The command below resolves the route the kernel would use for that destination, including the interface and the source it would pick:

ip route get 10.8.0.5
ip route get 10.8.0.5 from 192.0.2.44
ip rule show
ip route show table all | sed -n '1,40p'

VPN clients often install policy rules at priorities below the main table, so a clean main table proves nothing on its own. Compare the rule priorities with the tables they select, and check whether the tunnel route survives a reconnect instead of being added by hand once.

Policy-based IPsec adds a third matching stage that routing does not cover, as strongSwan's forwarding notes describe for tunnel and transport setups. The security policy selects traffic by prefix and protocol, while the route only decides where an unprotected packet would go. A subnet that is routed but has no matching selector either leaves unprotected or gets dropped, depending on configuration, and neither outcome looks like a firewall problem. Print the policy and compare its selectors against the prefixes you expected the tunnel to carry.

Filtering applies at a hook, not at a route

A rule that permits the VPN listener is a rule about traffic addressed to the gateway itself. It says nothing about clients reaching hosts behind that gateway, because forwarded traffic traverses a different chain. Reading the rule set as one flat list is the usual reason a correct-looking rule never fires.

Counters settle the question quickly. A rule with a zero packet count was never reached, which points at the wrong chain, the wrong interface, or an earlier drop. A rule with a growing count under a drop policy is the boundary you were looking for, and the packet is being discarded rather than misrouted.

Send a probe through the path the client uses

Tests run on the gateway itself travel through output and input, with the gateway's own source address. That is a different path from a client's forwarded request, so a successful local curl proves less than it appears to. Probe from the machine that fails, or pin the interface and source explicitly.

nc -vz -w 3 10.8.0.5 443
ping -c 3 -W 1 10.8.0.5
tcpdump -ni any host 10.8.0.5 and port 443
iptables -vnL FORWARD --line-numbers
nft list ruleset | sed -n '1,60p'

A capture on both ends separates the remaining cases. A SYN that leaves the client and never arrives means routing, encryption or an upstream access list. A SYN that arrives and draws no reply means the service or its host filter, and the tunnel was never the problem.

Return traffic deserves its own look. Asymmetric routing plus reverse path filtering drops replies that arrive on the wrong interface, which shows up as a one-way connection rather than a blocked port. Check the sysctl value before blaming the firewall for a symptom it did not cause.

Logging works as a second opinion, not a first step. A temporary log statement on the forward chain names the interface and addresses that hit it, and it is easy to remove afterwards. Leave it in place permanently and you have built a log volume problem instead of an answer.

Put each change where it survives a reconnect

A route added with ip route add lives until the interface goes down. Persist it in the connection profile instead: NetworkManager accepts extra routes on the connection, and netplan keeps them across boots. The same rule applies to a client-side firewall exception, which is gone after the next reload.

Server-side rules belong in the configuration file the service loads at start, not in an interactive session that ends when you close it. Write the rule, reload, and confirm the counter moved, because a reload that rejected the syntax silently leaves the old rule set in place.

Then repeat the original application request end to end and record which layer changed. A route fix that also needed a filter change will still fail, and the second failure reads much more easily once you know the first is gone. Note the date and the client version beside the result, since routing and filtering defaults both move between releases. Keep that note on the ticket so the next person starts where you stopped instead of repeating the two-stage walk.

Ngaahi tokoni fekau'aki