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.

Active-active versus active-passive gateway failover On the left, a load balancer or anycast layer spreads traffic across two active gateway nodes; if one fails, health checks remove it and the other keeps serving. On the right, a virtual IP floats between a primary and standby node managed by VRRP or keepalived; the standby claims the IP only when the primary stops advertising. Active-Active Load Balancer / Anycast / DNS Gateway A serving Gateway B serving both nodes carry traffic; on failure the LB drops the dead node after a failed check Active-Passive Virtual IP (VRRP / keepalived) Primary holds VIP, serving Standby idle, watching standby claims the VIP only when primary stops advertising (VRRP dead-timer)

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_script that 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.



← High Availability Topologies