Active-Active vs Active-Passive Gateway Failover
When an API gateway node dies, the only question that matters is how long — and how much state — you lose before traffic is flowing again. The two architectures that answer that question are active-active, where every node serves live traffic and a failure simply removes one from the pool, and active-passive, where a single primary serves while a standby waits to claim its identity the instant it detects the primary is gone. The choice is not cosmetic: it dictates your failover time, whether split-brain is a risk, how you handle session and counter state, and how configuration must be synchronized so the surviving node can actually serve the traffic it inherits. This page compares the two across Kong 3.x, NGINX Plus R32+, and Envoy 1.32+.
The scenario and what is at stake
You run a gateway tier in front of production APIs with a recovery time objective measured in seconds. A node will eventually fail — a kernel panic, a bad deploy, a rack losing power, a network partition. The design decision you make now determines whether that failure is a non-event or a visible outage. Under-design it and a single node loss drops every in-flight request and takes seconds of downtime; over-design it and you inherit the operational tax of distributed state and split-brain avoidance that your traffic volume may not justify.
Prerequisite concepts
This comparison builds directly on high availability topologies, which covers redundancy models and health-checking mechanics, and on the broader API gateway fundamentals and architecture that separates the control plane (which owns configuration) from the data plane (which serves requests). Failover capacity is meaningless if the surviving nodes cannot absorb the redirected load, so read this alongside scaling limits and capacity planning: an active-active pool that runs each node at 80% utilization has no headroom to absorb a peer’s traffic when one node fails.
The two topologies
The diagram contrasts how traffic reaches the gateway and what happens when a node dies in each model.
In active-active, the intelligence lives in the traffic-distribution layer — a load balancer, anycast routing, or DNS — and the gateway nodes are interchangeable. In active-passive, the intelligence lives in the failover protocol between the two nodes; the network sees a single virtual IP that moves.
Active-active: mechanics and configuration
Active-active means every node is live. Traffic is spread by an external distributor, and failover is nothing more than that distributor noticing a node is unhealthy and routing around it. Because the distributor is already load-balancing, a node loss removes capacity but never requires a role change.
The hard requirement is that request handling be stateless across nodes. Any node must be able to serve any request, which means per-request policy state — rate-limit counters, session data, cache — cannot live on a single node’s memory. Kong solves this by backing rate limiting with a shared Redis cluster so the counter is consistent no matter which node serves the request:
# Kong 3.x — rate-limiting with a shared Redis backend for cross-node consistency
plugins:
- name: rate-limiting
config:
minute: 600
policy: redis # shared counter, not per-node "local"
redis:
host: redis.internal
port: 6379
timeout: 2000
fault_tolerant: true # degrade gracefully if Redis is unreachable
sync_rate: 0 # 0 = strict shared counter, no local batching
With policy: redis and sync_rate: 0, every node reads and increments the same counter, so a consumer’s quota is enforced globally across the active-active pool rather than multiplied by the node count. The load balancer in front runs a health check that pulls a node out of rotation after one or two consecutive failures. Envoy expresses this as an upstream cluster of gateway endpoints with active health checking and outlier detection:
# Envoy 1.32+ — active-active gateway pool with health checks and outlier ejection
clusters:
- name: gateway_pool
connect_timeout: 1s
lb_policy: LEAST_REQUEST
load_assignment:
cluster_name: gateway_pool
endpoints:
- lb_endpoints:
- endpoint: { address: { socket_address: { address: 10.0.1.10, port_value: 8000 } } }
- endpoint: { address: { socket_address: { address: 10.0.1.11, port_value: 8000 } } }
health_checks:
- timeout: 1s
interval: 2s
unhealthy_threshold: 2
healthy_threshold: 2
http_health_check: { path: "/status/ready" }
outlier_detection:
consecutive_5xx: 3
interval: 5s
base_ejection_time: 30s
Failover time here is effectively the health-check detection window — interval times unhealthy_threshold, so roughly two to four seconds before a dead node stops receiving new connections, and often faster because a connection refused is detected immediately. In-flight requests on the failed node are lost unless the client or an upstream retry replays them.
Active-passive: mechanics and configuration
Active-passive means one node owns a floating virtual IP and serves everything; the standby holds no traffic until it promotes. Promotion is driven by a heartbeat protocol — most commonly VRRP via keepalived — in which the primary continuously advertises its priority and the standby claims the VIP when those advertisements stop:
# keepalived.conf — VRRP for an active-passive NGINX Plus R32+ gateway pair
vrrp_instance gw_vip {
state MASTER
interface eth0
virtual_router_id 51
priority 150 # standby is configured lower, e.g. 100
advert_int 1 # advertisement every 1s
# dead-timer = 3 * advert_int = 3s before standby promotes
authentication {
auth_type PASS
auth_pass changeme
}
virtual_ipaddress {
10.0.0.100/24
}
track_script {
chk_gateway # demote if the local gateway health check fails
}
}
vrrp_script chk_gateway {
script "/usr/bin/curl -sf http://127.0.0.1:8000/status/ready"
interval 2
weight -60 # drop priority below standby on failure
}
The track_script is the piece teams forget: without it, VRRP fails over only when the node dies, not when the gateway process dies. Tracking a local health check and lowering priority on failure means a hung gateway on a healthy host still triggers promotion. NGINX Plus complements this with cluster-aware zone synchronization (zone_sync) so that rate-limit and sticky-session state is replicated to the standby, letting it inherit counters rather than starting cold. Failover time is the VRRP dead-timer — typically 3 × advert_int, so about three seconds with a one-second interval — during which the VIP is unowned and requests fail.
Session and counter state
State is where the two models genuinely diverge. Active-active forbids node-local state by construction: counters and sessions must live in a shared store (Redis, a database) or the model breaks the moment traffic lands on a different node than last time. Active-passive tolerates node-local state only if it is replicated to the standby before failover; otherwise the standby promotes with empty counters, momentarily resetting every consumer’s rate limit and dropping every sticky session. The lesson is symmetric: neither model lets you keep authoritative state in a single node’s memory and survive a failure cleanly.
Split-brain and config sync
Split-brain is an active-passive-specific hazard. If a network partition cuts the heartbeat but both nodes are alive, the standby promotes while the primary keeps serving — now two nodes answer ARP for the same VIP and traffic flaps. The defenses are a tie-breaker (a third witness node or an out-of-band health path the two nodes cannot both lose) and fencing that guarantees the demoted primary stops serving before the standby takes over. Active-active sidesteps split-brain entirely because there is no single owned identity to contend for — but it pays for that with the distributed-consistency cost of shared counters.
Both models share one non-negotiable prerequisite: configuration must be identical and current on every node before it takes traffic. A failover node running stale routes is worse than a failed node. Kong hybrid mode streams config from the control plane to all data-plane nodes; NGINX Plus pushes config to every node through the same automation and synchronizes runtime zones; Envoy pulls RouteConfiguration and Cluster resources from an xDS control plane. In all three, config distribution is a control-plane concern that must be solved before failover can be trusted.
Decision matrix
| Dimension | Active-Active | Active-Passive |
|---|---|---|
| Failover time | Sub-second to a few seconds (health-check window) | VRRP dead-timer, typically ~3s |
| Capacity utilization | Full — every node serves | Half — standby idle |
| Failure of one node | Loses that node’s share of capacity | Full outage until standby promotes |
| State handling | Must be shared (Redis / DB); no node-local state | Node-local allowed if replicated to standby |
| Split-brain risk | None (no owned identity) | Real; needs tie-breaker + fencing |
| Traffic distribution | External LB / anycast / DNS | Floating virtual IP (VRRP / keepalived) |
| Operational complexity | Distributed counters, health tuning | Heartbeat tuning, fencing, standby drift |
| Best fit | High volume, stateless policy, RTO ~0 | Simpler stacks, modest volume, few-second RTO acceptable |
Default to active-active when traffic volume justifies always-on redundant capacity and your policy layer is already stateless or Redis-backed. Choose active-passive when the stack is simpler, a few seconds of failover is within your recovery time objective, and you would rather tune a heartbeat than operate a distributed counter tier.
Gotchas and failure signals
Sizing active-active at full utilization. If each node in an N-node pool runs near capacity, losing one node overloads the survivors and the failover cascades into a total outage. Size so the pool absorbs at least one node loss — the headroom math is covered in scaling limits and capacity planning.
VRRP without process tracking. A keepalived config that fails over only on node death leaves you exposed to a hung gateway process on a healthy host. Always attach a track_script that demotes on a local health-check failure.
Silent config drift on the standby. An active-passive standby that is not receiving the same config pushes as the primary will promote with stale routes. Alert on config-version mismatch between primary and standby, not just on liveness.
Counter reset on active-passive failover. Without state replication, promotion resets every rate-limit counter, briefly letting all consumers exceed quota simultaneously — a self-inflicted traffic spike at the worst possible moment. Replicate counters or accept and document the reset window.
Health checks that are too shallow. A TCP-connect health check passes while the gateway returns 503 for every request. Use an HTTP readiness path (/status/ready) that exercises the real request path so an unhealthy-but-listening node is ejected.
Validation
- Failover time measured under a real node kill (not a graceful drain) and confirmed within the recovery time objective.
- Active-active pool sized to absorb at least one node loss without exceeding the survivors’ safe utilization.
- Rate-limit and session state is shared (active-active) or replicated to the standby (active-passive), verified by a failover test that checks counters survive.
- VRRP/keepalived uses a
track_scriptthat demotes on gateway process failure, not only host failure. - Split-brain defense in place: tie-breaker witness and fencing tested by simulating a heartbeat partition.
- Config version is identical across all nodes before any node takes traffic; drift is alerted.
- Health check exercises a real request path (HTTP readiness), not just TCP connect.
FAQ
Is active-active always better than active-passive for API gateways?
No. Active-active gives you near-zero failover time and full capacity utilization, but it demands stateless request handling, cross-node consistency for rate-limit counters, and a load balancer or anycast layer to spread traffic. Active-passive is simpler: one node serves, a standby waits, and VRRP or keepalived moves a virtual IP on failure. If your gateway holds per-node session state or your team cannot operate distributed counters, active-passive with a few seconds of failover is the safer default. The right answer depends on your recovery time objective and how stateless your policy layer is.
How fast is failover in active-passive versus active-active?
Active-active failover is effectively instant for new requests because healthy nodes are already serving; the load balancer simply stops sending traffic to the failed node after one or two failed health checks, typically under a second. Active-passive failover is the time for the standby to detect the primary is gone and claim the virtual IP, governed by the VRRP advertisement interval and dead-timer, usually one to three seconds. In-flight requests on the failed node are lost in both models unless the client retries.
What causes split-brain in active-passive gateway clusters?
Split-brain happens when the standby stops receiving VRRP advertisements from the primary but the primary is actually still alive — usually a network partition between the two nodes rather than a real failure. Both nodes then believe they own the virtual IP and both answer ARP for it, causing traffic to flap. The mitigation is a tie-breaker: a third witness, an out-of-band health signal, or fencing that guarantees the demoted node stops serving before the standby promotes.
How do I keep gateway configuration consistent across failover nodes?
Configuration must be pushed from a single source of truth to every node atomically, never edited per node. Kong hybrid mode streams config from the control plane to all data-plane nodes; NGINX Plus uses cluster-aware zone synchronization and config pushed by the same automation to every node; Envoy pulls RouteConfiguration and Cluster resources from an xDS control plane. In every case the failover node must already hold the current config before it takes traffic, so config sync and health checking are prerequisites for safe failover, not afterthoughts.
Related
- High Availability Topologies — redundancy models, health-checking, and the broader HA design space these failover patterns sit inside.
- Scaling Limits & Capacity Planning — sizing the pool so surviving nodes absorb a failover without cascading.
- API Gateway Fundamentals & Architecture — control-plane vs data-plane separation that makes config sync across failover nodes possible.