Active/Active Firewalls the Routing Way: Palo Alto, BGP, and a 3-Second Hold Timer

Two Palo Altos, both forwarding, no floating IPs: eBGP to the N9K border pair and ECMP does the rest. Then a failover test found 90 seconds of silence, BFD refused to help, and the live re-test revealed that recovery, not failure, is where packets go to die.
palo alto active/active banner

On this page

The problem with the classic Active/Active

Most firewall HA designs lean on layer 2 tricks: a floating IP, gratuitous ARP on failover, and a prayer that every switch in the path honors the MAC move quickly. It works until it doesn't, and when it doesn't, you're debugging ARP caches at 2 AM. There's a cleaner shape available if your firewalls sit next to switches that speak BGP: stop treating the pair as one virtual device wearing a shared IP, and start treating them as two ordinary routers that happen to sync state.

That's how my Palo Alto pair runs. Both firewalls forward traffic all the time, no floating IP on the inside path, no ARP games, and failover is a routing event instead of a layer 2 scramble.

Topology: two Palo Alto firewalls, each LACP-bundled to an N9K vPC pair, four eBGP sessions, ECMP default route from the fabric
Each firewall bonds into the N9K vPC pair and speaks eBGP to both border leaves. The fabric sees one default route with two equal next-hops.

The design in one paragraph

Each firewall bundles two interfaces into an LACP aggregate toward the border pair and runs eBGP (AS 65300) against both leaves (AS 65205). Each advertises a default route it learned from its upstream interface. The border leaves run maximum-paths in the tenant VRF, so the fabric's default route is ECMP across both firewalls: flows hash across the pair and both boxes earn their power draw. HA still exists underneath, but for different jobs: HA1 carries control, HA2 syncs the session table so either box can pick up the other's flows, and HA3 handles the packets that arrive at the firewall that doesn't own the session, because with ECMP on both sides, asymmetry isn't an accident, it's the design. Session ownership goes to the first packet's firewall; setup load is spread by IP modulo.

PAN-OS HA state showing Active-Active mode, active-primary state, and complete state synchronization
Both boxes active, session state synchronization Complete. The HA cluster is real; it just doesn't own the data path.
Border leaf showing ECMP default route via both firewalls and the tuned BGP timers
The fabric's view: one default, two next-hops, and the timers that make failure detection honest.

Here is the configuration on both sides of that peering. On the N9K side, three lines carry the whole design: advertise l2vpn evpn turns the fabric's EVPN routes into ordinary BGP advertisements toward the firewalls, maximum-paths 8 makes the two firewall next-hops coexist, and timers 1 3 is the failover story we'll get to:

N9K BGP configuration for the tenant VRF and the PAN-OS BGP peer configuration in set format
The whole peering, both sides. Nothing exotic: a VRF, two neighbors, aggressive timers, and an LACP bundle for the firewall to stand on.

What the fabric shares, and what it hears back

The route exchange is where this design connects to everything behind it. The workloads live on a VXLAN/EVPN fabric; their subnets and the Kubernetes LoadBalancer VIP pool travel the fabric as EVPN type-5 routes and arrive at the border leaves. The border leaves re-advertise them to the firewalls as plain eBGP, and you can read the topology right out of the AS path: every prefix says 65100 (the cluster nodes) then the leaf pair it entered through:

Routes the leaf advertises to the firewall, showing fabric AS paths, and the two routes the firewall advertises back
Thirteen prefixes south to north: node subnets, per-node host routes, and the LoadBalancer VIP pool. Two prefixes north to south: a default route and one segment that lives behind the firewalls.

The asymmetry of that exchange is the security model in miniature. The firewalls learn everything about the fabric because they need to route inbound traffic to published services and steer return paths. The fabric learns almost nothing back: a default route (the way out) and one directly attached segment. Externally, north of the firewalls, no routing protocol runs at all: outbound traffic is source-NATed to the firewalls' own upstream addresses, and inbound reaches published services through destination NAT on floating addresses, so the upstream network never needs a route to any internal prefix. Routing stops at the firewall on purpose; NAT and published VIPs carry the conversation from there.

Why not Active/Passive?

Fair question, because A/P is genuinely simpler and most shops should run it. Three things pushed this design to A/A. Capacity: with ECMP, both boxes carry production traffic, so you know your failover capacity is real because you're using it every day; an idle standby is a promise nobody has tested. Failure blast radius: losing one of two active paths degrades to half capacity instantly, with no role transition, no preempt decision, no state promotion. And honesty about asymmetry: once the fabric ECMPs toward the pair and the upstream does its own path selection, packets will arrive at the wrong box sometimes no matter what you do. A/A with HA3 packet forwarding makes that a designed-for case instead of an outage: the non-owner firewall forwards the packet over HA3 to the session owner, inspection happens exactly once, and nobody drops anything.

NAT is where the A/P and A/A worlds differ most, and it's worth naming here because it shapes the design. In Active/Passive, NAT is a non-question: one box forwards, so it owns the source-NAT pool, answers the destination-NAT addresses, and holds every translation; on failover the standby inherits all of it (sessions included, thanks to HA2) and carries on with the same addresses. In Active/Active both boxes translate simultaneously, and that splits NAT down the middle: source NAT must become per-device, because two dataplanes cannot share one port-allocation pool, so each firewall NATs outbound traffic to its own upstream address. Destination NAT stays pair-wide, because rewriting a published address to an internal one is stateless and either box can do it for new sessions. Outbound flows therefore leave with two different public identities depending on which firewall ECMP picked, which is invisible to almost everything but matters the day something whitelists your egress IP: whitelist both. The full mechanics, including the commit errors that enforce this split, are the next post.

Session ownership deserves one more sentence, because two knobs control it. The session owner (who inspects the flow) is the firewall that saw the first packet; the session setup device (who does the initial work) is spread by IP modulo. Between them, load distributes without either box needing to agree with the switches about hashing, and HA2 keeps both session tables identical so a mid-flow failover has somewhere to land.

The part that didn't work: 90 seconds of silence

The first failover test was humbling. Kill a firewall and the affected flows black-holed for tens of seconds. The reason is BGP's dirty secret: by default, a dead neighbor is detected by the hold timer, and the default hold timer is 90 seconds. The leaves happily forwarded into a corpse until the timer expired.

The textbook fix is BFD, and the textbook is wrong for this environment: the N9Ks here would not run it reliably. The workable fix was aggressive BGP timers on the firewall-to-leaf sessions: keepalive 1, hold 3, configured on both sides. Two gotchas earned their place in my notes. NX-OS won't renegotiate timers on an established session, so the sessions must bounce after the change. And Palo Alto HA has a testing trap: an admin suspend keeps BGP politely alive, so it never triggers fast convergence. If you test failover with request high-availability state suspend, you'll conclude your tuning did nothing. Test by shutting the firewall's port-channel on the leaves instead.

What failure actually looks like: three regimes

I re-ran the failover live for this post: three continuous pings from a workload through the fabric, then firewall-a's bundle shut on both leaves. The results are more interesting than a single number:

Failover test results: zero loss during the link-down failure, 5.4 seconds of loss on recovery rehash
The failure nobody noticed and the recovery that cost 5 seconds.
The whole story in fifteen seconds: ECMP through both firewalls, fw-a dies and traffic reroutes without a drop, then the failback rehash.

The whole story in fifteen seconds: ECMP through both firewalls, fw-a dies and traffic reroutes without a drop, then the failback rehash.

Regime one: link-down failure. When the bundle dropped, the leaves tore the BGP session down with the link and withdrew the route in milliseconds. ECMP shrank from two next-hops to one, and not a single ping was lost. The hold timer never entered the conversation.

Regime two: silent death. If the box dies while its links stay up (a hung dataplane, a hypervisor freeze), nothing tears the session down for you, and the hold timer is the whole story. That's the case the 1/3 timers exist for: measured at roughly 5 seconds of loss when we tuned it, down from 30 to 48 seconds with defaults.

Worth pausing on regime one, because it's the quiet win of the whole design. Nothing was configured to make that happen: no BFD, no fast-hello protocol, no tuning. A directly connected eBGP session dies with its link, and route withdrawal plus ECMP shrink is as fast as the control plane can program hardware. The expensive machinery is only for failures the link state can't tell you about.

Regime three, the surprise: recovery. When firewall-a's bundle came back and its BGP sessions re-established, the route rejoined ECMP and one of my three flows rehashed onto the returning box mid-stream, dropping 25 packets over 5.4 seconds while the new path settled. Failback is not free. If your change window cares about that, bring the box back with its BGP session administratively damped and release it deliberately.

The takeaway

The nugget inside the deep dive: Active/Active firewalls get dramatically simpler when you let routing do the failover. Both boxes forward, ECMP spreads the load, link-down converges in milliseconds for free, and the one genuinely hard case (silent death) is handled by three seconds of hold timer. The complexity doesn't vanish, it moves: into NAT rules that must be written per-device and a config-sync feature with sharp boundaries. That's the next post.

Subscribe to LevelUp I.T. newsletter and stay updated.

Don't miss anything. Get all the latest posts delivered straight to your inbox. It's free!
Great! Check your inbox and click the link to confirm your subscription.
Error! Please enter a valid email address!