Pings work. DNS works. But half your TCP connections hang at “connecting…” and never finish. The cause is almost invisible: traffic leaves by one path and tries to come back by another, and a stateful firewall on the return path drops it because it never saw the connection start. This is asymmetric routing meeting stateful inspection — and it is maddening until you can see it.
A stateful firewall remembers every connection it has seen: when a client sends a TCP SYN outbound, the firewall writes a conntrack entry so it can recognise the matching SYN-ACK coming back and let it in. That is the whole value of stateful inspection — you only have to allow the outbound, and the return is permitted automatically because the firewall saw it start.
Asymmetric routing breaks that assumption. If the packet leaves by one path and the reply comes back by another — a second WAN, a second firewall, an ECMP hash landing differently each way — then the box on the return path never saw the SYN, has no state for the flow, and drops the SYN-ACK as an unsolicited packet. The connection half-opens and hangs. Nothing is “down”; the path simply has a one-way valve nobody designed on purpose.
01
The SYN goes out one way; the SYN-ACK comes back the other, into a firewall that has no record of the flow, and dies there.
The giveaway is that something works. Stateless traffic (much of UDP, ICMP) sails through because there is no state to miss, so ping and often DNS are fine. TCP is where it bites: the three-way handshake cannot complete because the SYN-ACK is dropped, so the client sits retransmitting its SYN until it gives up.
02
Each ingredient is normal. Put two exits and a stateful box together without pinning the path, and you have built a one-way valve.
| Cause | How the path splits |
|---|---|
| Dual-WAN without flow pinning | Out via WAN-A (default route), reply arrives on WAN-B because the server was reached via B is address — or the return is routed by a rule that differs from the forward decision. |
| Two firewalls / HA pair without state sync | Forward through one node, return through the other; the second has no conntrack entry for the flow. |
| ECMP / multipath | Per-packet or re-hashed load balancing sends the two directions down different next-hops through different stateful boxes. |
| Policy routing that only marks one direction | A PBR rule steers outbound but the return is left to the normal table, so it comes back elsewhere. |
03
“Ping works but nothing loads” plus a maddening inconsistency — some destinations fine, others dead — points straight here.
| What you see | What it tells you |
|---|---|
| Ping and DNS fine, TCP connections hang | Stateless passes, stateful return is dropped — the signature. |
| Some sites work, others never do | Which path a flow takes depends on the destination and the hash — symmetric ones work, asymmetric ones do not. |
| Conntrack shows SYN_SENT stuck | The firewall recorded the outbound SYN but no reply ever matched it. |
| Started after a second WAN / HA pair went in | The change that introduced a second path is the cause. |
04
Pick one of two strategies — pin the return to the forward path, or make the stateful boxes share state — and apply it deliberately.
The usual fix on a dual-WAN edge is connection marking: mark a connection when its first packet arrives on a WAN, then force every packet of that connection — including the return — back out the same WAN, regardless of the routing table. This is the single most useful pattern for keeping an edge symmetric.
# remember which WAN a NEW connection used, and mark its packets /ip firewall mangle # mark the connection by the WAN its first packet arrived on add chain=prerouting in-interface=ether1-wanA connection-state=new \ action=mark-connection new-connection-mark=from-A passthrough=yes # replies from a server behind us: force them back out that same WAN (transit) add chain=prerouting connection-mark=from-A in-interface-list=LAN \ action=mark-routing new-routing-mark=to-A passthrough=no # replies from the router's own services go via output, not prerouting add chain=output connection-mark=from-A action=mark-routing new-routing-mark=to-A # route to-A marked traffic back out WAN-A; same pair for WAN-B /ip route add dst-address=0.0.0.0/0 gateway=wanA-gw routing-mark=to-A
On Cisco the equivalent is policy-based routing that matches the connection and sets the egress, or keeping the firewall pair state-synchronised (a failover/HA cluster that shares its connection table so either node recognises the flow). For an HA firewall, enabling state sync is the correct fix; for ECMP, make the hash per-flow (not per-packet) and ensure both directions hash the same, or remove the second stateful box from one direction.
It is tempting to “fix” asymmetry by telling the firewall to accept invalid/out-of-state packets, or to not track the flow at all. That makes the symptom disappear and throws away the entire point of a stateful firewall — now unsolicited SYN-ACKs from anywhere are accepted. Fix the path, not the firewall. If you must relax state, scope it to the exact flow, never globally.
05
Prove the direction: the SYN leaves, the SYN-ACK is seen on the wire but never on the firewall that should pass it.
# RouterOS: a connection stuck in SYN_SENT with no reply bytes /ip firewall connection print where tcp-state=syn-sent # capture on BOTH WANs at once: SYN out on A, SYN-ACK in on B = asymmetric /tool sniffer quick interface=ether1-wanA,ether2-wanB protocol=tcp # Linux/host side: SYN retransmits with no handshake completion sudo tcpdump -ni any 'tcp[tcpflags] & (tcp-syn) != 0 and host SERVER'
If the SYN goes out one interface and the SYN-ACK arrives on another — or arrives at a firewall that logs it as invalid/no-state — you have asymmetry. Pin the flow and it completes immediately.
Takeaways
Asymmetric paths through a stateful firewall are one of the hardest faults to spot from the symptom. I trace the flow both directions, find where the return vanishes, and pin the paths so connections complete.
Book a Discovery Call →