Site-to-Site VPN: The Routing Checklist That Prevents One-Way Traffic

Was this helpful?

One-way traffic over a site-to-site VPN is the kind of failure that looks “sort of fine” from far away. The tunnel is up. Phase 1 and Phase 2 are green. Someone can ping something. And yet the application is dead, or only works in one direction, or only when the moon is in a particular subnet.

In production, one-way traffic is rarely a “VPN problem.” It’s a routing problem wearing a VPN costume, with NAT and MTU often playing supporting roles. This is the routing-first checklist I use when I need answers quickly and I don’t have time to admire the tunnel’s uptime graph.

The mental model: why “tunnel up” is not “traffic flows”

A site-to-site VPN has two distinct jobs:

  1. Keying and encryption: authenticate peers, negotiate algorithms, build Security Associations (SAs).
  2. Forwarding traffic: decide which packets enter the tunnel, and where the return packets go.

Most NOC dashboards obsess over job #1. Job #2 is where the bodies are.

One-way traffic is a routing integrity failure

“One-way” usually means one of these:

  • Asymmetric routing: packet goes through the tunnel one way, returns via the internet (or another path) and gets dropped.
  • Wrong encryption domain / selectors: the source/destination doesn’t match the negotiated policies, so traffic never enters the tunnel (or only enters in one direction).
  • NAT in the wrong place: the forward path is NATed but the reverse path expects original addresses (or vice versa).
  • Stateful firewall mismatch: return packets aren’t recognized as established because they arrive on a different interface/path.
  • PMTU / fragmentation: small pings work; TCP stalls; only certain applications fail. Looks “one-way” at L7.
  • Overlapping subnets: both sides think 10.0.0.0/8 is “local.” Spoiler: it’s not local on both sides.

Here’s the simplest production rule I know: for every VPN-protected prefix on Side A, Side B must have an unambiguous return route back through the tunnel, and vice versa. If you can’t state that out loud with exact CIDRs, you’re not done.

One quote to keep you honest (paraphrased idea): Werner Vogels: everything fails; design assuming failure, and verify behavior rather than trusting status lights.

Joke #1: A VPN tunnel “up” without working routes is like a hotel lobby with no rooms—beautiful, useless, and somehow still expensive.

Fast diagnosis playbook (first/second/third)

If you only have 15 minutes and people are slacking you screenshots of “Phase 2: up,” do this. In order. No detours.

First: prove the path in both directions

  • Pick one source host on Side A and one destination host on Side B.
  • Test ICMP and TCP (ICMP alone lies—politely).
  • Run traceroute (or better: tracepath) both ways and compare.

Decision: if either direction doesn’t enter the tunnel interface/gateway you expect, stop blaming IPsec. Fix routing or policy selection.

Second: verify selectors / routes and return routes

  • Policy-based IPsec: verify the traffic selectors (local/remote subnets) on both sides match exactly.
  • Route-based IPsec (VTI): verify kernel routes point remote prefixes to the VTI, and that reverse paths do the same.
  • Check for overlaps, supernet mismatches, and “temporary” static routes still lingering.

Decision: if routing tables look right but return traffic still doesn’t come back, you’re likely looking at NAT, firewall state, or asymmetric routing through a second WAN.

Third: PMTU/MSS and NAT rules

  • If ping works but apps don’t: suspect MTU/MSS.
  • If only certain subnets fail: suspect NAT exemptions, wrong ACL order, or selector mismatch.
  • If it works one direction only: suspect stateful firewall or return route (yes, again).

Decision: apply MSS clamping for TCP, verify PMTU with tracepath, and ensure NAT exemptions cover exactly the VPN-protected CIDRs.

Checklists / step-by-step plan (do this in order)

Checklist 1: Define your encryption domain like you mean it

  1. Write down Side A protected prefixes (exact CIDRs) and Side B protected prefixes.
  2. Confirm there are no overlaps. If there are, stop and renumber or use NAT intentionally (not accidentally).
  3. Decide route-based (VTI) vs policy-based. If you can choose, choose route-based for operational sanity.
  4. Document which IPs should be reachable across the tunnel, and which must not.

Checklist 2: Make routing symmetric by construction

  1. For each remote prefix, confirm there is exactly one best route: via the VPN.
  2. If you run dynamic routing (BGP/OSPF), confirm:
    • routes are advertised in both directions
    • filters allow the intended prefixes
    • metrics/communities prefer the VPN path
  3. If you run static routes, ensure:
    • they’re on both sides
    • they’re not shadowed by a broader route (like a default route)
  4. Enable reverse path filtering thoughtfully (rp_filter)—strict mode can break valid multi-homing.

Checklist 3: Decide where NAT is allowed, then enforce it

  1. Make a NAT policy statement: “Traffic between A-protected and B-protected is not NATed.” (That’s the default you want.)
  2. Implement NAT exemptions before general internet masquerade rules.
  3. If you must NAT across the tunnel (overlaps, vendor constraint), NAT consistently and document the mapped ranges.

Checklist 4: Make MTU boring

  1. Measure path MTU across the tunnel (don’t guess).
  2. Clamp TCP MSS on the tunnel interface if you see stalls.
  3. Allow ICMP “fragmentation needed” messages—or PMTU discovery breaks and you get mysterious black holes.

Checklist 5: Firewalls and state

  1. Confirm both sides allow:
    • IKE (UDP 500)
    • NAT-T (UDP 4500) if NAT is in the path
    • ESP (IP proto 50) if not using NAT-T
  2. Confirm policy for the protected subnets is symmetrical (both directions).
  3. On stateful firewalls, confirm return traffic re-enters via the same logical path; if not, design around it.

Checklist 6: Observability that catches one-way traffic early

  1. Monitor tunnel state and data plane: bytes/packets, drops, and per-selector counters.
  2. Run synthetic probes both directions (ICMP + TCP).
  3. Log key routing changes (BGP sessions, route table diffs) around incident windows.

Practical tasks with commands: outputs, meaning, and decisions

Below are field-tested tasks you can run on typical Linux VPN gateways and routers. Each task includes: command, what the output means, and the decision you make.

Task 1: Confirm the kernel route to the remote subnet (Linux)

cr0x@server:~$ ip route get 10.20.30.40
10.20.30.40 via 169.254.100.2 dev vti0 src 10.10.10.1 uid 0
    cache

Meaning: The system will send packets to 10.20.30.40 through vti0 (route-based tunnel) via the VTI peer IP.

Decision: If this doesn’t point to the tunnel interface (or points to the internet/default gateway), fix routing before touching IPsec settings.

Task 2: Show the route table and catch shadowed routes

cr0x@server:~$ ip route show table main
default via 203.0.113.1 dev eth0
10.10.0.0/16 dev lan0 proto kernel scope link src 10.10.10.1
10.20.0.0/16 via 169.254.100.2 dev vti0 proto static metric 100

Meaning: Remote 10.20.0.0/16 is explicitly routed into the tunnel; good. If you saw only default via, you’d have a return-path roulette situation.

Decision: If the remote prefix is missing, add it (or fix dynamic routing). If it’s present but a broader route wins (wrong metric), correct metrics/policy routing.

Task 3: Verify IPsec SAs exist and are carrying traffic (strongSwan)

cr0x@server:~$ sudo swanctl --list-sas
vpn-siteA-siteB: #12, ESTABLISHED, IKEv2, rekeying in 2 hours
  local  'gw-siteA' @ 198.51.100.10[4500]
  remote 'gw-siteB' @ 203.0.113.20[4500]
  AES_GCM_16_256/PRF_HMAC_SHA2_256/ECP_256
  CHILD_SA vpn-child{34}:  INSTALLED, TUNNEL, reqid 1
    10.10.0.0/16 === 10.20.0.0/16
    bytes_i 1289021, bytes_o 1401122, packets_i 9801, packets_o 10210

Meaning: SAs are up, and counters show packets in/out. If bytes_i grows but bytes_o doesn’t (or vice versa), you have one-way data plane.

Decision: If SA counters don’t move while you generate traffic, your selectors don’t match or routing isn’t sending packets to IPsec/VTI.

Task 4: Confirm policy-based selectors at the kernel (XFRM policy)

cr0x@server:~$ sudo ip xfrm policy
src 10.10.0.0/16 dst 10.20.0.0/16 
    dir out priority 2345 ptype main 
    tmpl src 198.51.100.10 dst 203.0.113.20 proto esp reqid 1 mode tunnel
src 10.20.0.0/16 dst 10.10.0.0/16 
    dir in priority 2345 ptype main 
    tmpl src 203.0.113.20 dst 198.51.100.10 proto esp reqid 1 mode tunnel

Meaning: The kernel will encrypt traffic from 10.10/16 to 10.20/16 outbound, and accept inbound traffic the other way.

Decision: If one direction is missing or uses different CIDRs, fix the IPsec policy/connection definition. Mismatched selectors are a classic “works from A to B but not back” generator.

Task 5: Check for asymmetric routing via policy routing rules

cr0x@server:~$ ip rule show
0:      from all lookup local
100:    from 10.10.0.0/16 lookup 100
32766:  from all lookup main
32767:  from all lookup default

Meaning: Traffic sourced from 10.10/16 uses routing table 100. This can be correct (multi-WAN, VRFs), or a hidden trap.

Decision: If policy routing exists, confirm the remote prefixes are in the same table. If not, return traffic might exit the wrong interface.

Task 6: Inspect table 100 to verify remote routes exist there too

cr0x@server:~$ ip route show table 100
default via 198.51.100.1 dev eth1
10.20.0.0/16 via 169.254.100.2 dev vti0 metric 50

Meaning: Even under policy routing, 10.20/16 still goes into vti0. Good.

Decision: If 10.20/16 is missing here, add it; otherwise some flows will go to the internet and die on a stateful firewall or peer ACL.

Task 7: Check reverse path filtering (rp_filter) that drops asymmetric return traffic

cr0x@server:~$ sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.vti0.rp_filter
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.vti0.rp_filter = 1

Meaning: Strict-ish source validation is enabled. This can drop legitimate packets if routing is asymmetric or if source routes appear on different interfaces.

Decision: If you have multi-homing or policy routing, consider setting rp_filter=2 (loose) on relevant interfaces, but only after understanding why asymmetry exists.

Task 8: Confirm NAT exemption (iptables) so VPN traffic is not masqueraded

cr0x@server:~$ sudo iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-A POSTROUTING -s 10.10.0.0/16 -d 10.20.0.0/16 -j RETURN
-A POSTROUTING -s 10.10.0.0/16 -o eth0 -j MASQUERADE

Meaning: Traffic from 10.10/16 to 10.20/16 bypasses NAT; everything else to eth0 is masqueraded.

Decision: If the RETURN rule is missing or below MASQUERADE (order matters), fix it. NATing VPN traffic is an easy way to create “it pings but nothing else works” weirdness.

Task 9: Confirm nftables NAT ordering (if you use nft)

cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table ip nat {
  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    ip saddr 10.10.0.0/16 ip daddr 10.20.0.0/16 return
    oifname "eth0" ip saddr 10.10.0.0/16 masquerade
  }
}

Meaning: Same idea as iptables: exempt VPN traffic first.

Decision: If your exemption is absent or too broad (returning more than intended), tighten it. Sloppy NAT exemptions are how you accidentally bypass security boundaries.

Task 10: Verify firewall filter rules allow the protected subnets both ways

cr0x@server:~$ sudo iptables -S FORWARD
-P FORWARD DROP
-A FORWARD -i vti0 -o lan0 -s 10.20.0.0/16 -d 10.10.0.0/16 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A FORWARD -i lan0 -o vti0 -s 10.10.0.0/16 -d 10.20.0.0/16 -j ACCEPT
-A FORWARD -i vti0 -o lan0 -s 10.20.0.0/16 -d 10.10.0.0/16 -j ACCEPT

Meaning: Forwarding is default-drop; explicit allows exist. Note the established/related rule helps return traffic, but you still need symmetric allows depending on policy.

Decision: If only one direction is allowed, you’ll see one-way TCP (SYN goes out, SYN/ACK dies). Fix the missing direction and confirm state tracking sees the right interfaces.

Task 11: Capture traffic on the LAN and tunnel interfaces to prove where it dies

cr0x@server:~$ sudo tcpdump -ni lan0 host 10.20.30.40 and '(icmp or tcp)'
14:22:01.112233 IP 10.10.10.50.51522 > 10.20.30.40.443: Flags [S], seq 1234567890, win 64240, options [mss 1460,sackOK,TS val 1 ecr 0], length 0
14:22:02.113344 IP 10.10.10.50.51522 > 10.20.30.40.443: Flags [S], seq 1234567890, win 64240, options [mss 1460,sackOK,TS val 2 ecr 0], length 0

Meaning: SYNs are leaving the LAN side; no SYN/ACK returns. Now capture on vti0 to see if they enter the tunnel.

Decision: If packets appear on LAN but not on tunnel, routing/policy selection is wrong. If they appear on tunnel but not back on LAN, return routing/firewall on the far side is wrong.

Task 12: Capture on VTI to confirm packets traverse the tunnel

cr0x@server:~$ sudo tcpdump -ni vti0 host 10.20.30.40 and tcp port 443
14:22:01.112999 IP 10.10.10.50.51522 > 10.20.30.40.443: Flags [S], seq 1234567890, win 64240, options [mss 1360,sackOK,TS val 1 ecr 0], length 0

Meaning: SYN is in the tunnel. Note the MSS changed to 1360—someone is clamping, which might be good.

Decision: If the packet is in the tunnel, the local side is likely fine; focus on the remote gateway’s return route, firewall, NAT, or selector mismatch.

Task 13: Check PMTU across the tunnel with tracepath

cr0x@server:~$ tracepath -n 10.20.30.40
 1?: [LOCALHOST]                                         pmtu 1500
 1:  169.254.100.2                                        1.023ms
 2:  10.20.30.40                                          2.108ms reached
     Resume: pmtu 1436 hops 2 back 2

Meaning: The discovered PMTU is 1436. If you’re sending 1500-byte frames without fragmentation handling, something will black-hole.

Decision: If PMTU is below 1500, clamp MSS (Task 14) and ensure ICMP “frag needed” isn’t blocked.

Task 14: Apply TCP MSS clamping on the tunnel interface (Linux, iptables)

cr0x@server:~$ sudo iptables -t mangle -A FORWARD -o vti0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
cr0x@server:~$ sudo iptables -t mangle -S FORWARD | tail -n 3
-A FORWARD -o vti0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Meaning: New TCP connections traversing vti0 will have MSS adjusted to match PMTU, reducing fragmentation pain.

Decision: If TCP stalls disappear after clamping, you’ve confirmed an MTU/PMTU issue. Keep it, then clean up underlying MTU or ICMP filtering if possible.

Task 15: Verify IKE/ESP/NAT-T reachability (from the gateway)

cr0x@server:~$ sudo ss -uapn | egrep ':(500|4500)\b'
UNCONN 0      0           0.0.0.0:500        0.0.0.0:*    users:(("charon",pid=1234,fd=12))
UNCONN 0      0           0.0.0.0:4500       0.0.0.0:*    users:(("charon",pid=1234,fd=13))

Meaning: The daemon is listening for IKE and NAT-T. If 4500 is missing but NAT exists, you may negotiate poorly or fail after initial packets.

Decision: If listeners aren’t present, fix service configuration. If they are present but no traffic arrives, focus on upstream ACLs/security groups.

Task 16: Check for drops on the VTI interface

cr0x@server:~$ ip -s link show dev vti0
10: vti0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1436 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    RX:  bytes packets errors dropped  missed   mcast
       8821991  82310      0     212       0       0
    TX:  bytes packets errors dropped carrier collsns
       9011122  84100      0       0       0       0

Meaning: RX drops exist. Drops can be MTU, firewall, queueing, or policy issues, but it’s a hard signal that packets aren’t making it through cleanly.

Decision: If drops increment during tests, correlate with tcpdump and logs; consider MTU, rate limiting, or misrouted packets hitting wrong interface.

Task 17: Validate forwarding is enabled (still a classic)

cr0x@server:~$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

Meaning: The box is allowed to route packets. If it’s 0, you can have a perfect tunnel and a gateway that behaves like a very secure paperweight.

Decision: If it’s 0, enable it and persist the setting; then re-test flows.

Task 18: Check BGP route exchange over the tunnel (FRR)

cr0x@server:~$ sudo vtysh -c "show ip bgp summary"
BGP router identifier 10.10.10.1, local AS number 65010
Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd
169.254.100.2   4      65020     10422     10398       77    0    0 2d03h          12

Meaning: BGP neighbor over the VTI is up and receiving prefixes (12). If it’s idle/active, you don’t have routing symmetry yet.

Decision: If prefixes received are 0 or unexpected, inspect filters/route-maps. If BGP is down, verify tunnel interface reachability and firewall on TCP/179.

Task 19: Confirm the specific prefix is installed and preferred (BGP)

cr0x@server:~$ sudo vtysh -c "show ip route 10.20.0.0/16"
Routing entry for 10.20.0.0/16
  Known via "bgp", distance 20, metric 0, best
  * 169.254.100.2, via vti0, weight 1, 2d03h

Meaning: The remote prefix is best via the tunnel. If the best path is via an internet next-hop, you’ve built a one-way machine.

Decision: Fix local preference/metrics, or add a higher-priority static route into the VTI while you stabilize BGP policy.

Task 20: Verify conntrack sees the flow (stateful firewall path issues)

cr0x@server:~$ sudo conntrack -L | grep "10.10.10.50.*10.20.30.40.*dport=443" | head -n 2
tcp      6 118 SYN_SENT src=10.10.10.50 dst=10.20.30.40 sport=51522 dport=443 [UNREPLIED] src=10.20.30.40 dst=10.10.10.50 sport=443 dport=51522

Meaning: The connection is stuck in SYN_SENT with UNREPLIED. That’s consistent with missing return traffic (routing/firewall/selector).

Decision: If you see replies but the application still fails, move up-stack (TLS, app ACLs). If you don’t, focus on return path and remote side policy.

Common mistakes: symptoms → root cause → fix

1) Symptom: “Ping works A→B, but B→A fails”

  • Root cause: return route on Side B points to a different gateway (often a default route or a second WAN), so replies bypass the tunnel.
  • Fix: install explicit routes for Side A protected prefixes via the tunnel/VTI on Side B; ensure policy routing tables also include them. Verify with ip route get both ways.

2) Symptom: “Tunnel is up, but nothing passes; counters don’t move”

  • Root cause: selector mismatch (policy-based) or routing doesn’t send traffic to the tunnel (route-based).
  • Fix: align local/remote CIDRs exactly on both peers; verify XFRM policy or VTI routes exist and match intended traffic.

3) Symptom: “Small pings work; HTTPS hangs or loads half a page”

  • Root cause: MTU/PMTU black hole; ICMP frag-needed blocked; TCP segments too large after encapsulation overhead.
  • Fix: measure PMTU with tracepath; clamp MSS on tunnel; allow ICMP type 3 code 4 where appropriate.

4) Symptom: “Only some subnets work over VPN”

  • Root cause: incomplete selectors, missing static routes, or route filtering in BGP/OSPF.
  • Fix: audit prefixes end-to-end: advertised, accepted, installed, and allowed by firewall/NAT exemptions. Don’t trust “we added the /16.” Prove it with route lookups.

5) Symptom: “Works for a while, then one-way after a failover”

  • Root cause: HA pair failover changes source IP, breaks selectors, or shifts return traffic to a different interface. Conntrack state is lost or mismatched.
  • Fix: ensure stable tunnel endpoints (VIPs), consistent routing and NAT rules across HA nodes, and test failover with bidirectional probes.

6) Symptom: “BGP says prefixes are received, but traffic still one-way”

  • Root cause: routes are installed, but asymmetric due to a different best path for return, or rp_filter drops packets arriving on unexpected interface.
  • Fix: compare routing decisions with ip route get on both sides; adjust local preference/metrics; set rp_filter appropriately for the design.

7) Symptom: “Only UDP works; TCP is flaky”

  • Root cause: MTU/MSS plus retransmission sensitivity; or stateful firewall with asymmetric return path.
  • Fix: MSS clamp, confirm symmetric routing, and validate conntrack sees both directions consistently.

8) Symptom: “Traffic enters tunnel but remote host never sees it”

  • Root cause: remote side routes traffic into the tunnel but lacks internal routing to the destination LAN, or remote firewall blocks after decapsulation.
  • Fix: traceroute from remote gateway to internal host; ensure internal routers know the return route to the VPN-protected prefixes; fix segmentation policies.

Three corporate mini-stories (how this fails in real life)

Mini-story #1: The incident caused by a wrong assumption

Two business units merged networks without merging opinions. One team ran the on-prem firewall/VPN; the other controlled a cloud VPC. They agreed on a site-to-site IPsec tunnel and declared victory when the UI said “Connected.”

The wrong assumption was subtle: the cloud team assumed “routes propagate automatically because the tunnel is up.” The on-prem team assumed “the cloud always has a route back because it’s a cloud.” Neither assumption was tested with a bidirectional flow.

Forward traffic from on-prem to cloud worked because on-prem had explicit static routes to the cloud CIDRs. Return traffic failed because the cloud side had the correct VPN attachment but the route table associated with the workload subnet didn’t include the on-prem CIDRs. Some subnets used a different route table entirely, so one app worked and another didn’t. That “partial success” bought them a full day of confusion.

Fixing it was boring: associate the right route table to the right subnets, add the missing routes, and then run the same two probes every time: ICMP and TCP from both directions. The lasting improvement was cultural: they added “prove return route” as a change-control checkbox, not a tribal memory.

Mini-story #2: The optimization that backfired

A network team wanted faster convergence and fewer moving parts. They replaced a pair of static routes with BGP over the IPsec tunnel. Smart. They also tuned BGP to prefer a secondary internet circuit as “backup,” using local preference and a route-map that looked elegant in a review.

Then an unrelated maintenance event flipped which circuit was “primary.” BGP re-converged exactly as configured, which is the worst kind of correct: it preferred the secondary circuit for some prefixes, but the IPsec endpoint was still bound to the primary circuit’s public IP. The data plane became asymmetrical: outbound traffic went into the tunnel; return traffic was drawn to the other exit because the route-map said so.

Users reported it as “random timeouts.” Monitoring showed the tunnel up and BGP established. Packet captures told the truth: SYNs went out, SYN/ACKs came back on the wrong interface and were dropped by stateful rules and rp_filter. The “optimization” wasn’t BGP; it was the assumption that control plane preference always implies data plane symmetry.

The fix was to make the design explicit: tie BGP next-hops to the tunnel interface (VTI), not to a WAN-dependent path; use consistent policy routing; and test failover as a first-class scenario. They kept BGP, but stopped treating it like magic.

Mini-story #3: The boring but correct practice that saved the day

A company with multiple sites had a rule: every VPN change includes a before/after snapshot of three things—routes, NAT rules, and a bidirectional probe result. No exceptions. Engineers rolled their eyes, because engineers are paid partly in eye-rolls.

During a routine firewall upgrade, a new default NAT rule was introduced earlier in the rule order. The tunnel stayed up, and basic pings from the firewall itself worked. But application traffic from the LAN started failing in a way that looked like a remote outage.

The change ticket contained the snapshots. Comparing NAT tables made the issue obvious: VPN traffic was now being masqueraded, breaking selectors on the far end and confusing return routing. They reverted the rule order within minutes, then reintroduced it with a properly placed NAT exemption. The incident never became a full-blown outage because the diagnostic artifacts were already there.

Boring practice, dramatic payoff. The secret isn’t genius; it’s repeatability. Your future self will not remember that one special NAT rule you “definitely” won’t forget.

Joke #2: If you want to find your hidden NAT rule, schedule an outage window—NAT rules love attention and will appear immediately.

Interesting facts and short history (context that prevents mistakes)

  • Fact 1: IPsec’s core standards originated in the 1990s, and the design shows: it’s powerful, flexible, and allergic to simple troubleshooting.
  • Fact 2: ESP (Encapsulating Security Payload) is IP protocol 50, not a TCP/UDP port—so “opening ports” is insufficient if you’re not using NAT-T.
  • Fact 3: NAT-T (UDP 4500) became common because NAT breaks classic IPsec assumptions; encapsulating ESP in UDP made IPsec survivable on the modern internet.
  • Fact 4: Policy-based VPNs (selectors/ACLs define what’s encrypted) were historically common on firewalls; route-based VPNs (VTI) became popular as networks demanded dynamic routing and clearer operations.
  • Fact 5: Path MTU discovery depends on ICMP. Blocking ICMP entirely is the operational equivalent of removing road signs and being surprised by traffic jams.
  • Fact 6: “Tunnel up” checks often validate only IKE negotiation, not that traffic selectors match or that routes exist; that’s why synthetic traffic probes matter.
  • Fact 7: Asymmetric routing is not inherently wrong. It becomes wrong when you introduce stateful firewalls, conntrack, rp_filter, or strict anti-spoofing assumptions.
  • Fact 8: Overlapping RFC1918 networks (10/8, 172.16/12, 192.168/16) are a top driver for “temporary NAT” across VPNs—which then becomes permanent architecture through inertia.
  • Fact 9: Cloud VPNs frequently have separate concepts for “tunnel attachment” and “route table association.” People configure one and forget the other, then wonder why return traffic vanishes.

FAQ

1) Why does one-way traffic happen if Phase 2 is established?

Because Phase 2 being established means SAs exist, not that packets match selectors, not that routes send packets into the tunnel, and not that return routes exist.

2) Should I prefer route-based VPN (VTI) or policy-based VPN?

Prefer route-based (VTI) when you can. It aligns with how routing actually works, supports dynamic routing cleanly, and is easier to observe and reason about.

3) How do I quickly prove it’s routing, not “the VPN”?

Run ip route get for the remote host on both gateways, then capture packets on LAN and tunnel interfaces with tcpdump. If packets don’t enter the tunnel, it’s routing/policy selection. If they enter but don’t return, it’s return routing or remote firewall/NAT.

4) Why does ping work but TCP fails?

MTU/PMTU issues and blocked ICMP frag-needed messages are common. Also, some environments deprioritize or rate-limit ICMP differently than TCP. Measure PMTU with tracepath and clamp MSS if needed.

5) Do I need to allow ICMP through firewalls for VPNs?

You should allow the ICMP types needed for PMTU discovery along the path, especially “fragmentation needed.” Blanket-blocking ICMP is how you get silent black holes and long incident calls.

6) What’s the fastest way to detect asymmetric routing?

Compare traceroutes and route lookups from both sides. Also check conntrack states: a pile of SYN_SENT [UNREPLIED] is a strong hint that replies aren’t coming back the same way.

7) How do overlapping subnets cause one-way traffic?

If both sides use the same RFC1918 range, each side may consider the destination “local” and ARP for it instead of routing to the tunnel. Or the wrong route wins. If renumbering isn’t possible, use explicit NAT across the tunnel and document it aggressively.

8) Does NAT-T always mean NAT exists in the path?

Not always. Some stacks use UDP encapsulation by policy for compatibility. But if NAT exists and you don’t use NAT-T, expect pain: ESP doesn’t play nicely with address translation.

9) Can rp_filter break VPN traffic?

Yes. If packets arrive on an interface that the kernel doesn’t consider the best reverse path for the source, rp_filter can drop them. In multi-WAN or policy-routing designs, strict rp_filter is a frequent self-own.

10) What should I monitor to catch one-way issues early?

Monitor SA byte/packet counters per child SA, interface drops, and bidirectional synthetic probes (ICMP + TCP). Tunnel “up” alone is a feel-good metric, not an availability metric.

Conclusion: next steps that actually prevent recurrence

If you want to stop one-way traffic from returning like a seasonal allergy, do these next steps:

  1. Write the encryption domain down (exact CIDRs, both sides). Treat it like an API contract. Contracts don’t “mostly match.”
  2. Prove symmetric routing with ip route get on both gateways for a few representative hosts. Keep the outputs in the change record.
  3. Make NAT explicit: add VPN exemptions above masquerade rules, and review them whenever someone touches internet egress.
  4. Make MTU boring: measure PMTU, clamp MSS if needed, and stop blocking the ICMP you depend on.
  5. Automate bidirectional probes so you detect “tunnel up, traffic dead” within minutes, not from a ticket two days later.
  6. Test failover like it’s part of the system, because it is. Most one-way incidents are “works until something changes” stories.

Do the routing checklist first. It’s not glamorous, but it’s how you get a VPN that behaves like a network, not like a haunted house.

← Previous
Proxmox LDAP/AD Login Fails: Where the Auth Chain Breaks and How to Fix
Next →
Debian 13 mdadm RAID Degraded: Replace and Rebuild Without Data Loss

Leave a comment