Packets can follow an OpenVPN route and still be captured by an IPsec policy, and that combination looks like a routing bug from every angle except the right one. Linux resolves a route and then consults a separate security policy database, so a more specific destination route does not displace a matching XFRM policy. The visible symptom is traffic that leaves the host and never arrives, while both tunnels report themselves healthy. Three options resolve it, and which one you pick depends on which of the two paths is meant to carry that prefix.

Two decisions, not one

Route selection answers a narrow question: which next hop and source address should carry this packet. The security policy database answers a different one: does this packet require IPsec protection, and under which selectors. Both run for the same packet, and satisfying the first says nothing about the second. That is why changing a metric or adding a static route can leave the symptom untouched.

A broad IPsec selector is the usual cause. A policy protecting 172.16.0.0/12 also covers 172.20.8.0/24, which an administrator may intend to reach through the OpenVPN tunnel instead. The intent exists only in the routing table, and the policy database never learned about it.

Treat that as a conflict between two configured protection paths rather than a contest between two metrics. The fix belongs in whichever component was configured with the wrong scope.

Read the route the kernel actually picks

Start with the lookup the kernel performs, including the source address and any policy rule that could divert it to another table. A route in the main table may never be consulted if an earlier rule selects a different table, and longest-prefix matching only applies within the table a rule chooses.

ip rule show
ip route show table all
ip route get 172.20.8.20 from 10.44.7.15

The last command is the one that matters, because it prints the chosen route together with the source address the kernel would use. Compare that output with the selector the IPsec policy matches. When the route points at the OpenVPN interface and the policy still matches the same pair, you have reproduced the conflict without guessing.

Keep dumps free of unrelated addresses before they reach a ticket. A routing table from a production host carries internal topology, and only the lines that answer the question are worth pasting.

Read the policy that also applies

The policy listing shows direction, selectors and the template that decides what happens to a matching packet. Read the outbound direction first, then the inbound side for the return traffic, because a one-way exception produces a connection that starts and never completes.

ip -s xfrm policy
swanctl --list-sas

Compare the selectors in the listing with the prefixes the connection negotiated. Disabling strongSwan's route installation does not remove those policies, and changing a metric does not alter which source and destination pair a policy protects. Those two non-fixes are worth ruling out early because they are the ones people try first.

If the listing contains no policy for the prefix you expected, the problem is elsewhere and the routing table is probably telling the truth. If it contains a broader policy that covers the prefix, you have found the capture.

Three options when both paths are legitimate

Narrowing the IPsec selector is the cleanest option when the broad prefix was a convenience rather than a requirement. Reducing the remote traffic selector to the subnets that genuinely belong behind IPsec removes the conflict for every host at once, and it costs a rekey rather than a new component.

A passthrough exception is the surgical option when the broad selector must stay. strongSwan supports a separate connection whose child uses pass mode, which tells the kernel that the matched traffic must not be processed by IPsec. The loopback remote address keeps that connection from being selected for incoming peers, and the selectors must describe the real authorised pair rather than everything.

connections {
    openvpn-exception {
        remote_addrs = 127.0.0.1
        children {
            openvpn-exception {
                local_ts = 10.44.7.0/24
                remote_ts = 172.20.8.0/24
                mode = pass
                start_action = trap
            }
        }
    }
}

The strongSwan configuration examples document this policy type and the loopback peer convention. The third option is a route-based design with XFRM interfaces, which ties policy to traffic routed through a named interface instead of to a global selector. It is a migration rather than a tweak, and the traffic must still satisfy the negotiated selectors.

OptionUse it whenCost
Narrow the selectorThe broad prefix was convenienceA rekey and a policy review
Passthrough childThe broad selector must stayOne exception per intended pair
XFRM interfacePolicy should follow routingA design migration

Prove the exception without weakening the rest

After loading the change, test the exception and an ordinary destination that must stay protected. Check both directions, then drop the OpenVPN tunnel and confirm what happens to the excepted traffic. An exception that silently sends that prefix in the clear when the tunnel is down is a policy decision you should make on purpose, not discover during an incident.

Record which prefix uses which path and why. The next engineer to see a broad selector will otherwise re-open the same investigation, and the answer will be sitting in a commit message nobody reads.

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