Load balancing standards — sharing traffic is easy; failing well is not
Criteria verified as of August 2026. Re-verify on the web before committing to anything (§8).
1. Scope and triggers
Applies to designing, configuring and operating a load balancer or reverse proxy: choice of layer,
balancing algorithm, health checking, draining and zero-downtime deployment, TLS termination,
HTTP protocols, high availability of the balancer itself, stateless balancing, and protection of the
service edge.
Triggers: haproxy.cfg, nginx.conf (upstream, proxy_pass), Envoy bootstrap/xDS,
traefik.yml, Caddyfile (reverse_proxy), keepalived.conf, ipvsadm, "target group", "backend
service", "health check", "readiness", "drain", "sticky session", "consistent hashing", "maglev",
"PROXY protocol", "X-Forwarded-For", "SNI passthrough", "ECMP", "anycast", "outlier detection".
Not applicable: the catalogue already splits this up: networking-standards is the trunk (proxies and
balancing as a principle, VLAN, MTU, addressing) and already delegates the depth, while
routing-switching-standards owns the campus, BGP policy and control-plane security
—including the BGP announcement that makes anycast possible—, datacenter-fabric-standards owns the
fabric, VXLAN/EVPN and lossless Ethernet, and network-automation-standards configuration as
code. Outwards: caching and CDN belong to caching-cdn-standards, the service mesh and
discovery to microservices-architecture-standards, Service, Ingress and Gateway API to
kubernetes-standards, filtering to firewall-policy-standards, identity and OIDC to
identity-access-management-standards, TLS and PKI to cryptography-pki-standards, metrics and dashboards
to observability-standards, the SLO to sre-practice-standards, the measurement methodology and the
load model to performance-engineering-standards, the reactive method to
network-troubleshooting-standards, match-server session assignment to
gaming-infrastructure-standards (a game server is stateful and non-interruptible:
assigning a session is not balancing, and applying draining or per-request health to it breaks it),
and the umbrella of onprem-standards (alongside
datacenter-facilities-standards and hpc-standards). Among the three
sibling skills of this batch: wireless-standards is the access network, this one the service network and
high-speed-interconnect-standards the compute network; the domain's mistake is applying the
same criteria to all three.
Guiding principle: balancing is deciding what happens when something fails. Sharing traffic is the
trivial part; almost all the value is in the health check and in the draining. A balancer with
a good algorithm and a bad health check sends traffic towards broken servers with
exemplary precision.
2. Default decisions
Verify version, maintenance and raw licence before pinning any of these (§8).
| Decision |
Default |
Justifiable alternative / vetoed |
| Layer |
L7 (HTTP) when you need to route by path/header, retry, terminate TLS or observe requests |
L4 when throughput rules, the protocol is not HTTP or the encryption must reach the backend intact |
| Algorithm |
Least connections (or least requests) as a sensible default at L7 |
Round-robin only with homogeneous backends and uniform requests; consistent hashing when there is state or per-backend caching |
| Persistence |
None: stateless services and externalised sessions |
Affinity cookie only as a patch with a retirement date; ❌ source-IP affinity (NAT and mobiles break it) |
| Health check |
Your own endpoint, shallow and cheap, separate from business health |
Deep check only with a different threshold and without taking down the whole pool; ❌ TCP connect as the only signal for HTTP |
| Draining |
Mandatory: remove from the pool, wait for in-flight requests to finish, then stop |
❌ Kill the process and trust the client's retry |
| TLS |
Terminate at the balancer and re-encrypt towards the backend |
Passthrough when the backend must see the client certificate or compliance demands it; internal mTLS if the mesh does not cover it |
| Self-managed L7 proxy |
HAProxy (core GPL-2.0, headers LGPL) for observability and traffic control; Envoy (Apache-2.0) when dynamic xDS is needed |
nginx/Angie (both BSD-2-Clause) for familiarity; Traefik (MIT) in dynamic environments; Caddy (Apache-2.0) when the value is automatic ACME |
| High-throughput L4 |
IPVS in the kernel for the classic case; Cilium/XDP with Maglev-style hashing and DSR for large scale |
Katran only if you accept it is a low-cadence mirror of Meta's internal code; ❌ an L7 proxy as a throughput firewall |
| Balancer HA |
VRRP (RFC 9568) / keepalived with tested failover; ECMP + anycast when the volume justifies it |
❌ A balancer with no peer, or a pair whose failover was never exercised |
| HTTP towards the client |
HTTP/2 enabled; HTTP/3 only after verifying the exact state in your proxy and version |
❌ Assuming HTTP/3 is ready because the directive exists |
| HTTP towards the backend |
HTTP/1.1 with keepalive unless there is a reason; H2 towards backends changes the balancing (§3) |
❌ No keepalive towards the backend and then blaming the network |
3. Design criteria
L4 versus L7 — what you gain and what you lose
- L4 shares connections without understanding them: minimal cost per byte, any protocol, encryption
intact. You lose routing by path or header, per-request retry, health with application
semantics and all HTTP observability. L7 gives you all of that, but you pay CPU, latency
and a component that is part of your application's semantics (and of its attack surface).
- Rule: L4 for non-HTTP traffic and raw throughput; L7 as soon as the decision depends on the content.
Mixing layers in cascade (L4 in front, L7 behind) is legitimate and often the right thing.
Algorithms
- Round-robin: only with identical backends and requests of similar cost; otherwise it concentrates the
expensive ones in the same place. Least connections: a reasonable default because it approximates "who is least
busy", with mandatory slow start (a freshly restarted backend has zero connections and
takes an avalanche). Two random choices is cheap and very good with large lists.
- Consistent hashing (Maglev, ketama): mandatory when the backend holds useful state per
key —local cache, partitions, long sessions, per-tenant connections— and when the set
changes frequently: a modulo-N hash reassigns all keys when a node is added or removed; the
consistent one only its fraction. It is what allows several balancers to share the same way.
Session persistence: it is a smell
- Affinity ties a user to a server: it breaks draining, skews the sharing, turns every
deployment into session loss and hides the real bug, which is state in the process's memory. If
it exists, it is debt with an owner and a date; the solution is to move the session out. Legitimate exception:
long-lived connections (WebSocket, SSE), where the affinity is of the connection, not a cookie.
Health checks — the section that decides everything
- Shallow (
/healthz, no external dependencies) versus deep (are my dependencies
responding?): useful, but with different consequences.
- The capital danger: a check that queries the database means that, when the database hiccups,
the whole pool is marked unhealthy at once and the balancer withdraws 100% of the service for a
problem that only degraded a part —the total outage is caused by the check, not the failure—.
Mitigations: separate liveness from readiness, do not put shared dependencies into the
check that governs the sharing, and a panic threshold (if more than X% of the pool is unhealthy,
ignore health and share to all: degraded is better than switched off).
- Threshold asymmetry: go down fast (few failures), come back up slowly (several successes), so as not to
oscillate. Intervals, timeouts and thresholds are declared, not inherited from the default; the timeout must
be smaller than the interval or they overlap and falsify the state; and the check goes by the same
path as the real traffic (same port, same TLS).
- The application decides when it is ready:
/readyz must start failing before the
process begins shutting down. That is the real mechanism of draining.
Draining and zero-downtime deployment
- Mandatory sequence: mark as unavailable → wait for the balancer to notice (≥ one
full check cycle) → stop accepting new connections → finish the in-flight ones within a
deadline → close. Skipping the wait is the usual cause of 502s during deployments. The
grace period exceeds the longest legitimate request; long-lived connections are closed with an
orderly signal (GOAWAY in H2). Retries only on idempotent things and with a budget: retrying
everything under load turns a degradation into a storm.
TLS
- Terminating at the balancer simplifies certificates and gives visibility; re-encrypting towards the
backend is the default when the internal leg is not physically trusted —"it's the internal network" is not
an argument—, and mTLS when the balancer must prove who it is.
- Passthrough when the backend needs the client certificate or compliance forbids
decrypting: it is routed by SNI and everything else is lost. It is a decision, not a default. On
terminating you lose the real IP: PROXY protocol at L4, headers at L7 (§5).
HTTP/2 and HTTP/3 on the balancer
- H2 towards the client is the default. H2 towards the backend changes the balancing: it multiplexes many
requests onto few connections, so balancing per connection stops sharing —you need
per-request balancing, and even then a few persistent connections concentrate load—. It is the
classic cause of "uneven balancing" after enabling internal H2.
- H3/QUIC runs over UDP: it changes the firewall, ECMP (hashing over UDP and the Connection ID),
connection accounting and migration between networks. Verify the exact state per proxy and
version (§8): maturity differs between the client-proxy and the proxy-backend direction.
HA of the balancer itself
- The balancer is the single point of failure par excellence: it concentrates all traffic and all
connection state. A pair with VRRP (RFC 9568, which obsoletes 5798) or equivalent, with exercised
failover, knowing that it cuts in-flight connections unless there is state synchronisation.
- ECMP + anycast scales better: N identical balancers announcing the same VIP, with no active-passive
pair, no shared state and capacity that grows by adding nodes. The price: a
change in the set reshuffles the ECMP hash and breaks connections, unless the nodes use consistent
hashing towards the backends. It is what you need to know before buying a bigger appliance.
4. Quality gates
- Validate the configuration before applying it (
haproxy -c -f, nginx -t, envoy --mode validate
or the product's equivalent). A config that does not validate does not even reach staging.
- Backend failure test with real traffic: kill a backend and measure how many requests are
lost and how long it takes to be withdrawn. If nobody has measured it, the number is unknown, not zero.
- Zero-downtime deployment test under load with zero 5xx as the acceptance criterion (it detects
badly done draining), and a negative health test: degrade the shared dependency and
verify that the whole pool is not withdrawn — the gate that prevents the total outage.
- Balancer failover test, including the return (it fails more than the outbound leg), and a forwarding
header test: a spoofed
X-Forwarded-For from outside and the application does not believe it.
Config in the repo and applied by automation; a balancer that differs from its peer is a finding.
5. Security
X-Forwarded-For without trimming is a vulnerability. It is a list that anyone can
prepend to: if the application takes the first value, the attacker chooses their own IP and evades block
lists, rate limits, geolocation and auditing. Rule: the edge balancer
overwrites (does not append), or a fixed and known number of trusted proxies is counted from
the right. The same goes for Forwarded (RFC 7239), X-Forwarded-Proto/-Host and X-Real-IP.
- Strip at the edge every internal header the application uses to decide (roles, "is
internal", already-authenticated identity): an
X-Authenticated-User that survives from outside is a
complete authentication bypass.
- Rate limiting at the balancer by real IP and by credential/path, with 429 and limit headers:
it protects even with the application saturated. Per-IP limits are bypassed behind NAT or CGNAT;
combine them with per-identity limits.
- Flood protection: SYN cookies, per-source connection limits, aggressive handshake and header
timeouts (Slowloris is killed with a header read timeout), maximum body and header
size, and frame/stream limits in H2 (rapid reset is exhaustion, not throughput).
- The balancer's own surface: statistics and admin API never exposed;
certificates with automatic renewal and an expiry alert (ciphers and TLS, in
cryptography-pki-standards). And against request desynchronisation (request
smuggling), which arises from proxy and backend interpreting
Content-Length/Transfer-Encoding differently: reject ambiguous requests, normalise at the proxy and keep
versions up to date at both ends.
6. Performance and operability
- Signals that are always watched: requests per second and per code, latency at high
percentiles separating balancer queue from backend time, healthy versus configured backends,
active connections and their real distribution per backend, retries and 502/503/504 by cause.
- Overflow and queues: the accept queue (
backlog) and its kernel limit turn a
burst into lost connections; it is sized and its overflow is monitored. A large backlog
with no capacity behind it only swaps errors for latency.
- Ephemeral port exhaustion: opening a new connection per request towards few backends
exhausts the source port range and fails intermittently. Solution: keepalive towards the
backend with a sized pool, several source IPs if needed, and watch
TIME_WAIT.
- Timeout coherence: the balancer's idle timeout must be smaller than the backend's keepalive;
if the backend closes first, a dead connection gets reused and sporadic 502s appear
—the hardest failure in the domain to reproduce. Write them in a table (client,
balancer, backend, database) and verify they decrease. Capacity: size by
real percentiles and by concurrent connections; TLS and H2 consume memory per connection.
7. Sustainability and prohibitions
- Cadence: LTS/stable branches versus the latest minor; quarterly review and on any CVE with
relevant KEV/EPSS. The balancer is exposed: it is among the first to patch.
- nginx ecosystem (a datum that decides): nginx has belonged to F5 since 2019; in 2024 its lead
developer forked it into freenginx over governance disagreements, and since 2022 there is Angie,
from former core developers. All three share configuration and both forks are
BSD-2-Clause; nginx remains active (copyright through 2026), so it is not an emergency, but if
you choose nginx also decide whom you follow and verify each one's release cadence.
- Deprecation: every retired backend, rule and certificate disappears from the configuration and from the
inventory. A commented-out
server is not documentation.
FORBIDDEN
- ❌ A balancer with no active health check, or with TCP connect as the only signal for HTTP.
- ❌ A health check that queries the database or another shared dependency and can
mark the whole pool unhealthy at once, with no panic threshold.
- ❌ Deploying without draining: withdrawing from the pool and killing the process without waiting a health cycle.
- ❌ Permanent session affinity with no owner or retirement date; source-IP affinity.
- ❌ Trusting
X-Forwarded-For or Forwarded received from the client without overwriting or trimming.
- ❌ Letting internal trust or identity headers through to the backend.
- ❌ A single balancer for something that matters; or a pair whose failover has never been tested.
- ❌ Retrying non-idempotent requests, or retrying with no maximum budget.
- ❌ Enabling HTTP/3 without verifying its state in the specific version or adjusting firewall and ECMP for UDP.
- ❌ Backends without
keepalive (and then blaming the network for ephemeral port exhaustion), or
a balancer idle timeout greater than the backend keepalive (phantom 502s).
- ❌ Exposing the balancer's statistics page or admin API.
- ❌ Modulo-N hashing over a backend set that changes; consistent or nothing.
- ❌ Terminating TLS and speaking in the clear to the backend "because it's the internal network", with no written decision.
- ❌ Going to production without having measured how many requests are lost when a backend goes down.
8. Mandatory web verification
Methodology: the RFCs, one by one against the rfc-editor.org JSON; the licences, reading
the raw file from the repository, not the GitHub label.
RFCs verified Aug 2026: HTTP/3 = RFC 9114 (Jun 2022, Proposed Standard, nothing obsoleted);
HTTP/2 = RFC 9113 (Jun 2022, Proposed Standard, obsoletes 7540 and 8740 — citing RFC 7540 today is
a factual error); the Forwarded header = RFC 7239 (Jun 2014, Proposed Standard); VRRPv3 =
RFC 9568 (May 2024, Proposed Standard, obsoletes RFC 5798).
Licences verified raw Aug 2026: HAProxy — its LICENSE declares the core GPL v2 with
the explicit intent of allowing external modules, and develops the headers scheme under LGPL:
it is neither "plain GPL" nor MIT. nginx — 2-clause BSD, notice "Copyright (C) 2011-2026
Nginx, Inc." (active repository). Angie — the same BSD-2 text plus "Copyright (C) 2022-2026 Web
Server LLC": a fork with an identical licence, and its LICENSE is on the master branch, not main
(the usual path 404s and looks like a missing licence). Envoy and Caddy — Apache-2.0.
Traefik — MIT, in LICENSE.md, not LICENSE.
Declared discrepancy: sources diverge on HTTP/3 maturity in Envoy — the
downstream direction (client→proxy) is described as ready and the upstream one (proxy→backend) as alpha.
Treat it as unresolved and verify the documentation of your exact version.
Declared gaps — DO NOT fill from memory:
- Current stable versions and support windows of HAProxy, nginx, Angie, freenginx, Envoy,
Traefik and Caddy: deliberately not pinned here; in particular, freenginx's release cadence and
health are not verified.
- Exact HTTP/3 state per proxy and version, with its TLS-with-QUIC library dependencies and
connection migration: not verified product by product.
- Managed cloud balancers (ALB/NLB, Application Gateway/Front Door, backend services):
limits, draining, health semantics and HTTP/3 not verified here; they belong to
aws/azure/gcp.
- Katran is active but low-cadence and a mirror of Meta's internal code; Cilium/XDP and IPVS are not
pinned by version. And the concrete values of
backlog, health thresholds, grace
periods and pool sizes are engineering criteria, not measurements: they are derived by measuring.
If the web contradicts this document, the web wins — flag the discrepancy.
1---2name: load-balancing-standards3description: Load balancing as a failure-handling decision, not just traffic sharing — health checks, draining and TLS termination are where the value is. Use when designing or reviewing a load balancer or reverse proxy, haproxy.cfg with backend/server/option httpchk/http-check/observe/agent-check, nginx.conf upstream blocks with proxy_pass, keepalive and max_fails, an Envoy bootstrap or xDS cluster with outlier detection and panic threshold, Traefik static and dynamic configuration with healthCheck and serversTransport, a Caddyfile reverse_proxy with lb_policy and health_uri, an AWS ALB/NLB target group, Azure Application Gateway or Front Door, GCP backend service, choosing between layer 4 and layer 7, round-robin versus least-connections versus consistent hashing and maglev, session affinity and sticky cookies, shallow versus deep health check endpoints and a health check that queries the database, rise and fall thresholds, connection draining and graceful shutdown during a rolling deploy, TLS termination, re-encryption4---56# Load balancing standards — sharing traffic is easy; failing well is not78Criteria verified as of **August 2026**. Re-verify on the web before committing to anything (§8).910## 1. Scope and triggers1112Applies to **designing, configuring and operating a load balancer or reverse proxy**: choice of layer,13balancing algorithm, health checking, draining and zero-downtime deployment, TLS termination,14HTTP protocols, high availability of the balancer itself, stateless balancing, and protection of the15service edge.1617Triggers: `haproxy.cfg`, `nginx.conf` (`upstream`, `proxy_pass`), Envoy bootstrap/xDS,18`traefik.yml`, `Caddyfile` (`reverse_proxy`), `keepalived.conf`, `ipvsadm`, "target group", "backend19service", "health check", "readiness", "drain", "sticky session", "consistent hashing", "maglev",20"PROXY protocol", "X-Forwarded-For", "SNI passthrough", "ECMP", "anycast", "outlier detection".2122**Not applicable**: the catalogue already splits this up: `networking-standards` is the **trunk** (proxies and23balancing as a principle, VLAN, MTU, addressing) and **already delegates the depth**, while24`routing-switching-standards` owns the campus, BGP policy and control-plane security25—**including the BGP announcement that makes anycast possible**—, `datacenter-fabric-standards` owns the26fabric, VXLAN/EVPN and lossless Ethernet, and `network-automation-standards` configuration as27code. Outwards: **caching and CDN belong to `caching-cdn-standards`**, **the service mesh and28discovery to `microservices-architecture-standards`**, **Service, Ingress and Gateway API to29`kubernetes-standards`**, filtering to `firewall-policy-standards`, identity and OIDC to30`identity-access-management-standards`, TLS and PKI to `cryptography-pki-standards`, metrics and dashboards31to `observability-standards`, the SLO to `sre-practice-standards`, **the measurement methodology and the32load model to `performance-engineering-standards`**, the reactive method to33`network-troubleshooting-standards`, **match-server session assignment to34`gaming-infrastructure-standards`** (a game server is stateful and non-interruptible:35**assigning a session is not balancing**, and applying draining or per-request health to it breaks it),36and the umbrella of `onprem-standards` (alongside37`datacenter-facilities-standards` and `hpc-standards`). Among the three38sibling skills of this batch: `wireless-standards` is the **access network**, this one the **service network** and39`high-speed-interconnect-standards` the **compute network**; **the domain's mistake is applying the40same criteria to all three**.4142**Guiding principle**: **balancing is deciding what happens when something fails.** Sharing traffic is the43trivial part; **almost all the value is in the health check and in the draining**. A balancer with44a good algorithm and a bad health check sends traffic towards broken servers with45exemplary precision.4647## 2. Default decisions4849> Verify version, maintenance and **raw licence** before pinning any of these (§8).5051| Decision | Default | Justifiable alternative / vetoed |52|---|---|---|53| Layer | **L7 (HTTP)** when you need to route by path/header, retry, terminate TLS or observe requests | **L4** when throughput rules, the protocol is not HTTP or the encryption must reach the backend intact |54| Algorithm | **Least connections** (or least requests) as a sensible default at L7 | Round-robin only with homogeneous backends and uniform requests; **consistent hashing** when there is state or per-backend caching |55| Persistence | **None**: stateless services and externalised sessions | Affinity cookie only as a patch with a retirement date; ❌ source-IP affinity (NAT and mobiles break it) |56| Health check | **Your own endpoint, shallow and cheap**, separate from business health | Deep check **only** with a different threshold and without taking down the whole pool; ❌ TCP connect as the only signal for HTTP |57| Draining | **Mandatory**: remove from the pool, wait for in-flight requests to finish, then stop | ❌ Kill the process and trust the client's retry |58| TLS | **Terminate at the balancer and re-encrypt towards the backend** | Passthrough when the backend must see the client certificate or compliance demands it; internal mTLS if the mesh does not cover it |59| Self-managed L7 proxy | **HAProxy** (core GPL-2.0, headers LGPL) for observability and traffic control; **Envoy** (Apache-2.0) when dynamic xDS is needed | **nginx**/**Angie** (both BSD-2-Clause) for familiarity; **Traefik** (MIT) in dynamic environments; **Caddy** (Apache-2.0) when the value is automatic ACME |60| High-throughput L4 | **IPVS** in the kernel for the classic case; **Cilium/XDP** with Maglev-style hashing and DSR for large scale | **Katran** only if you accept it is a low-cadence mirror of Meta's internal code; ❌ an L7 proxy as a throughput firewall |61| Balancer HA | **VRRP (RFC 9568) / keepalived** with **tested** failover; **ECMP + anycast** when the volume justifies it | ❌ A balancer with no peer, or a pair whose failover was never exercised |62| HTTP towards the client | **HTTP/2 enabled; HTTP/3 only after verifying the exact state in your proxy and version** | ❌ Assuming HTTP/3 is ready because the directive exists |63| HTTP towards the backend | **HTTP/1.1 with keepalive** unless there is a reason; H2 towards backends **changes the balancing** (§3) | ❌ No `keepalive` towards the backend and then blaming the network |6465## 3. Design criteria6667**L4 versus L7 — what you gain and what you lose**68- **L4** shares connections without understanding them: minimal cost per byte, any protocol, encryption69 intact. **You lose** routing by path or header, per-request retry, health with application70 semantics and **all HTTP observability**. **L7** gives you all of that, but **you pay** CPU, latency71 and a component that is part of your application's semantics (and of its attack surface).72- **Rule**: L4 for non-HTTP traffic and raw throughput; L7 as soon as the decision depends on the content.73 Mixing layers in cascade (L4 in front, L7 behind) is legitimate and often the right thing.7475**Algorithms**76- **Round-robin**: only with identical backends and requests of similar cost; otherwise it concentrates the77 expensive ones in the same place. **Least connections**: a reasonable default because it approximates "who is least78 busy", with mandatory **slow start** (a freshly restarted backend has zero connections and79 takes an avalanche). **Two random choices** is cheap and very good with large lists.80- **Consistent hashing (Maglev, ketama)**: **mandatory** when the backend holds useful state per81 key —local cache, partitions, long sessions, per-tenant connections— and **when the set82 changes frequently**: a modulo-N hash reassigns *all* keys when a node is added or removed; the83 consistent one only its fraction. It is what allows several balancers to share the same way.8485**Session persistence: it is a smell**86- Affinity ties a user to a server: it breaks draining, skews the sharing, turns every87 deployment into session loss and hides the real bug, which is **state in the process's memory**. If88 it exists, it is debt with an owner and a date; the solution is to move the session out. Legitimate exception:89 long-lived connections (WebSocket, SSE), where the affinity is of the connection, not a cookie.9091**Health checks — the section that decides everything**92- **Shallow** (`/healthz`, no external dependencies) versus **deep** (are my dependencies93 responding?): useful, but **with different consequences**.94- **The capital danger**: a check that queries the database means that, when the database hiccups,95 **the whole pool is marked unhealthy at once** and the balancer withdraws 100% of the service for a96 problem that only degraded a part —the total outage is caused by the check, not the failure—.97 Mitigations: **separate liveness from readiness**, do not put shared dependencies into the98 check that governs the sharing, and a **panic threshold** (if more than X% of the pool is unhealthy,99 ignore health and share to all: degraded is better than switched off).100- **Threshold asymmetry**: go down fast (few failures), come back up slowly (several successes), so as not to101 oscillate. Intervals, timeouts and thresholds are declared, not inherited from the default; the **timeout must102 be smaller than the interval** or they overlap and falsify the state; and the check goes **by the same103 path** as the real traffic (same port, same TLS).104- **The application decides when it is ready**: `/readyz` must start failing **before** the105 process begins shutting down. That is the real mechanism of draining.106107**Draining and zero-downtime deployment**108- Mandatory sequence: mark as unavailable → **wait for the balancer to notice** (≥ one109 full check cycle) → stop accepting new connections → finish the in-flight ones within a110 deadline → close. Skipping the wait is the usual cause of 502s during deployments. The111 grace period exceeds the longest legitimate request; long-lived connections are closed with an112 orderly signal (GOAWAY in H2). **Retries only on idempotent things** and with a budget: retrying113 everything under load turns a degradation into a storm.114115**TLS**116- **Terminating at the balancer** simplifies certificates and gives visibility; **re-encrypting towards the117 backend** is the default when the internal leg is not physically trusted —"it's the internal network" is not118 an argument—, and **mTLS** when the balancer must prove who it is.119- **Passthrough** when the backend needs the client certificate or compliance forbids120 decrypting: it is routed by **SNI** and everything else is lost. It is a decision, not a default. On121 terminating you lose the real IP: **PROXY protocol** at L4, headers at L7 (§5).122123**HTTP/2 and HTTP/3 on the balancer**124- **H2 towards the client** is the default. **H2 towards the backend changes the balancing**: it multiplexes many125 requests onto few connections, so balancing per connection stops sharing —you need126 **per-request** balancing, and even then a few persistent connections concentrate load—. It is the127 classic cause of "uneven balancing" after enabling internal H2.128- **H3/QUIC runs over UDP**: it changes the firewall, ECMP (hashing over UDP and the **Connection ID**),129 connection accounting and migration between networks. **Verify the exact state per proxy and130 version** (§8): maturity differs between the client-proxy and the proxy-backend direction.131132**HA of the balancer itself**133- **The balancer is the single point of failure par excellence**: it concentrates all traffic and all134 connection state. A pair with VRRP (**RFC 9568**, which obsoletes 5798) or equivalent, with **exercised**135 failover, knowing that it **cuts in-flight connections** unless there is state synchronisation.136- **ECMP + anycast scales better**: N identical balancers announcing the same VIP, with no active-passive137 pair, no shared state and capacity that grows by adding nodes. The price: **a138 change in the set reshuffles the ECMP hash** and breaks connections, unless the nodes use consistent139 hashing towards the backends. It is what you need to know before buying a bigger appliance.140141## 4. Quality gates142143- **Validate the configuration before applying it** (`haproxy -c -f`, `nginx -t`, `envoy --mode validate`144 or the product's equivalent). A config that does not validate does not even reach staging.145- **Backend failure test with real traffic**: kill a backend and **measure** how many requests are146 lost and how long it takes to be withdrawn. If nobody has measured it, the number is unknown, not zero.147- **Zero-downtime deployment test** under load with **zero 5xx** as the acceptance criterion (it detects148 badly done draining), and a **negative health test**: degrade the shared dependency and149 verify that the whole pool is **not** withdrawn — the gate that prevents the total outage.150- **Balancer failover test**, including the **return** (it fails more than the outbound leg), and a **forwarding151 header test**: a spoofed `X-Forwarded-For` from outside and the application does not believe it.152 Config in the repo and applied by automation; a balancer that differs from its peer is a finding.153154## 5. Security155156- **`X-Forwarded-For` without trimming is a vulnerability.** It is a list that **anyone can157 prepend to**: if the application takes the first value, the attacker chooses their own IP and evades block158 lists, rate limits, geolocation and auditing. Rule: the edge balancer159 **overwrites** (does not append), or a **fixed and known** number of trusted proxies is counted from160 the right. The same goes for `Forwarded` (**RFC 7239**), `X-Forwarded-Proto/-Host` and `X-Real-IP`.161- **Strip at the edge every internal header** the application uses to decide (roles, "is162 internal", already-authenticated identity): an `X-Authenticated-User` that survives from outside is a163 complete authentication bypass.164- **Rate limiting at the balancer** by real IP and by credential/path, with 429 and limit headers:165 it protects even with the application saturated. Per-IP limits are bypassed behind NAT or CGNAT;166 combine them with per-identity limits.167- **Flood protection**: SYN cookies, per-source connection limits, aggressive handshake and header168 timeouts (Slowloris is killed with a header read timeout), maximum body and header169 size, and frame/stream limits in H2 (*rapid reset* is exhaustion, not throughput).170- **The balancer's own surface**: statistics and admin API **never** exposed;171 certificates with automatic renewal and an **expiry alert** (ciphers and TLS, in172 `cryptography-pki-standards`). And against **request desynchronisation** (*request173 smuggling*), which arises from proxy and backend interpreting174 `Content-Length`/`Transfer-Encoding` differently: reject ambiguous requests, normalise at the proxy and **keep175 versions up to date at both ends**.176177## 6. Performance and operability178179- **Signals that are always watched**: requests per second and per code, latency at **high180 percentiles** separating balancer queue from backend time, healthy versus configured backends,181 active connections and their **real distribution** per backend, retries and 502/503/504 by cause.182- **Overflow and queues**: the accept queue (`backlog`) and its kernel limit turn a183 burst into lost connections; it is sized and its **overflow is monitored**. A large `backlog`184 with no capacity behind it only swaps errors for latency.185- **Ephemeral port exhaustion**: opening a new connection per request towards few backends186 exhausts the source port range and fails intermittently. Solution: **keepalive towards the187 backend** with a sized pool, several source IPs if needed, and watch `TIME_WAIT`.188- **Timeout coherence**: the balancer's idle timeout must be **smaller** than the backend's keepalive;189 if the backend closes first, a dead connection gets reused and sporadic 502s appear190 —the hardest failure in the domain to reproduce. Write them in a table (client,191 balancer, backend, database) and verify they decrease. **Capacity**: size by192 real percentiles and by **concurrent connections**; TLS and H2 consume memory per connection.193194## 7. Sustainability and prohibitions195196- **Cadence**: **LTS/stable** branches versus the latest minor; quarterly review and on any CVE with197 relevant KEV/EPSS. The balancer is exposed: it is among the first to patch.198- **nginx ecosystem** (a datum that decides): nginx has belonged to **F5** since 2019; in 2024 its lead199 developer forked it into **freenginx** over governance disagreements, and since 2022 there is **Angie**,200 from former core developers. **All three share configuration and both forks are201 BSD-2-Clause**; nginx remains active (copyright through 2026), so it is not an emergency, but if202 you choose nginx **also decide whom you follow** and verify each one's release cadence.203- **Deprecation**: every retired backend, rule and certificate disappears from the configuration and from the204 inventory. A commented-out `server` is not documentation.205206**FORBIDDEN**207- ❌ A balancer with no active health check, or with TCP connect as the only signal for HTTP.208- ❌ A health check that queries the database or another shared dependency and can209 mark the whole pool unhealthy at once, with no panic threshold.210- ❌ Deploying without draining: withdrawing from the pool and killing the process without waiting a health cycle.211- ❌ Permanent session affinity with no owner or retirement date; source-IP affinity.212- ❌ Trusting `X-Forwarded-For` or `Forwarded` received from the client without overwriting or trimming.213- ❌ Letting internal trust or identity headers through to the backend.214- ❌ A single balancer for something that matters; or a pair whose failover has never been tested.215- ❌ Retrying non-idempotent requests, or retrying with no maximum budget.216- ❌ Enabling HTTP/3 without verifying its state in the specific version or adjusting firewall and ECMP for UDP.217- ❌ Backends without `keepalive` (and then blaming the network for ephemeral port exhaustion), or218 a balancer idle timeout greater than the backend keepalive (phantom 502s).219- ❌ Exposing the balancer's statistics page or admin API.220- ❌ Modulo-N hashing over a backend set that changes; consistent or nothing.221- ❌ Terminating TLS and speaking in the clear to the backend "because it's the internal network", with no written decision.222- ❌ Going to production without having **measured** how many requests are lost when a backend goes down.223224## 8. Mandatory web verification225226**Methodology**: the RFCs, **one by one** against the `rfc-editor.org` JSON; the licences, **reading227the raw file** from the repository, not the GitHub label.228229**RFCs verified Aug 2026**: **HTTP/3 = RFC 9114** (Jun 2022, Proposed Standard, nothing obsoleted);230**HTTP/2 = RFC 9113** (Jun 2022, Proposed Standard, **obsoletes 7540 and 8740** — citing RFC 7540 today is231a factual error); **the `Forwarded` header = RFC 7239** (Jun 2014, Proposed Standard); **VRRPv3 =232RFC 9568** (May 2024, Proposed Standard, **obsoletes RFC 5798**).233234**Licences verified raw Aug 2026**: **HAProxy** — its `LICENSE` declares the core **GPL v2** with235the explicit intent of allowing external modules, and develops the headers scheme under LGPL:236**it is neither "plain GPL" nor MIT**. **nginx** — **2-clause** BSD, notice "Copyright (C) 2011-2026237Nginx, Inc." (active repository). **Angie** — the same BSD-2 text plus "Copyright (C) 2022-2026 Web238Server LLC": **a fork with an identical licence**, and its `LICENSE` is on the **`master` branch, not `main`**239(the usual path 404s and looks like a missing licence). **Envoy** and **Caddy** — **Apache-2.0**.240**Traefik** — **MIT**, in `LICENSE.md`, not `LICENSE`.241242**Declared discrepancy**: sources diverge on HTTP/3 maturity in Envoy — the243downstream direction (client→proxy) is described as ready and the upstream one (proxy→backend) as alpha.244Treat it as unresolved and verify the documentation of **your** exact version.245246**Declared gaps — DO NOT fill from memory**:2471. **Current stable versions and support windows** of HAProxy, nginx, Angie, freenginx, Envoy,248 Traefik and Caddy: **deliberately not pinned here**; in particular, **freenginx's release cadence and249 health** are not verified.2502. **Exact HTTP/3 state per proxy and version**, with its TLS-with-QUIC library dependencies and251 connection migration: **not verified product by product**.2523. **Managed cloud balancers** (ALB/NLB, Application Gateway/Front Door, backend services):253 limits, draining, health semantics and HTTP/3 **not verified here**; they belong to `aws/azure/gcp`.2544. **Katran** is active but low-cadence and a mirror of Meta's internal code; **Cilium/XDP and IPVS are not255 pinned by version**. And the **concrete values** of `backlog`, health thresholds, grace256 periods and pool sizes are engineering criteria, **not measurements**: they are derived by measuring.257258If the web contradicts this document, **the web wins** — flag the discrepancy.