Put a CDN or cloud WAF with rate-based rules in front, and make the origin reachable only through it. At every proxy and server, bound each phase of a request: time to receive headers, time between body reads and for the whole request, idle keep-alive time, header count and size, and body size. Limit requests and concurrent connections per client and per expensive route, and answer with 429. Keep your HTTP/2 implementation patched for Rapid Reset (CVE-2023-44487), CONTINUATION floods, MadeYouReset (CVE-2025-8671) and the 2026 HPACK bomb, and cap concurrent streams. When demand still exceeds capacity, shed the cheapest-to-refuse traffic first with 503 and Retry-After instead of letting every request slow down.
- Slowloris holds connections open by sending headers slowly; slow POST does the same with the body. Each costs the attacker almost nothing and costs you a connection.
- Timeouts differ: nginx's
client_header_timeoutbounds the whole header, butclient_body_timeoutonly bounds the gap between reads. In our test a body trickled one byte every 3 seconds stayed open for 45 seconds against a 10-second setting. - Node.js and Envoy can bound the whole request. With
requestTimeoutorrequest_timeoutat 30 seconds, both answered the same trickled body with 408 after 30 to 31 seconds. - nginx's
limit_conncounts only connections whose headers are complete. Twenty-five half-sent requests from one address all stayed open under a limit of 20 until the header timeout closed them. - HTTP/2 lets one connection carry many requests. Rapid Reset peaked at 398 million requests per second against Google in 2023, and new HTTP/2 attacks followed in 2024, 2025 and 2026.
- Rate limits are only as good as their key: IP addresses spread across a botnet, so add per-session, per-API-key and per-route limits, and accept a forwarded client IP only from your CDN.
| Setting | Value | Note | Source |
|---|---|---|---|
nginx client_header_timeout | 10s (default 60s) | Bounds the whole request header. | https://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_timeout |
nginx client_body_timeout | 10s (default 60s) | Only the gap between two reads; pair it with limit_conn. | https://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_timeout |
nginx keepalive_requests | 1000 (default) | Also caps requests per HTTP/2 connection, which limited Rapid Reset. | https://www.f5.com/company/blog/nginx/http-2-rapid-reset-attack-impacting-f5-nginx-products |
nginx limit_req_status and limit_conn_status | 429 (default 503) | — | https://nginx.org/en/docs/http/ngx_http_limit_req_module.html |
Node.js headersTimeout and requestTimeout | 10 s and 30 s (defaults 60 s and 300 s) | Checked every connectionsCheckingInterval, 30 s by default. | https://nodejs.org/docs/latest-v24.x/api/http.html |
Node.js HTTP/2 maxConcurrentStreams | 100 | Node sets no limit by default. | https://nodejs.org/docs/latest-v24.x/api/http2.html |
Envoy request_headers_timeout and request_timeout | 10s and 30s | Both disabled by default. | https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto |
Envoy http2_protocol_options.max_concurrent_streams | 100 | Envoy's edge guidance; the default is 1024 per connection. | https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/edge |
| Overload response | 503 with Retry-After; 429 for per-client limits | — | https://www.rfc-editor.org/rfc/rfc9110.html#name-503-service-unavailable |
- Run the slow-client probe against each listener: a trickled header must be closed within your header timeout, and a trickled body within your total request timeout or connection limit.
- Send a burst of requests from one address to a rate-limited route and confirm the excess gets 429, and that a second address is unaffected.
- Hold more slow uploads than the per-client connection limit and confirm the extra connections are refused.
- Check every HTTP/2 terminator's version against the fixes for CVE-2023-44487, the CONTINUATION CVEs, CVE-2025-8671 and CVE-2026-49975 or CVE-2026-47774.
- From outside the CDN, request the origin directly and confirm it refuses the connection or the request.
- In a load test, push past capacity and confirm the service returns 503 with
Retry-Afterquickly instead of timing out, and recovers when the load stops.
- Applies to
- nginx 1.30.5 stable (tested); max_headers needs 1.29.8 or later
- Envoy 1.39.3 (tested)
- Node.js 24 LTS and 26 (tested on 26.3.0)
Internet-facing HTTP/1.1 and HTTP/2 services behind nginx, Envoy, a CDN or a cloud load balancer, and Node.js servers. Network-layer floods need your provider's or ISP's DDoS protection and are out of scope.
- Part of
- Workflows: Fix a finding, Respond to a vulnerability
Tested fixes: the vulnerable code, the fix, a plausible fix that still fails, and the regression tests that tell them apart.
Learning goals and prerequisites
What you will learn
- Explain how slow clients, HTTP floods and HTTP/2 stream abuse exhaust a server.
- Configure header, body and idle timeouts, size limits and per-client limits in nginx, Envoy and Node.js.
- Check whether your HTTP/2 stack has the fixes for Rapid Reset and the attacks that followed it.
- Combine edge rate limiting with origin protection and load shedding.
Before you start
Platform, SRE and backend engineers who run internet-facing HTTP services, proxies or CDNs
- Read nginx or Envoy configuration and HTTP request structure.
- Know where TLS terminates and which proxies sit in front of your service.
Refresh first: Bound API work, not just request counts
Go to the guided practiceA network flood fills your pipe; an application-layer (layer 7) attack sends requests that look valid and makes your servers spend their limited resources on them. Those resources are connections and worker slots, memory for buffered headers and bodies, CPU for TLS handshakes, routing and decompression, and the capacity of whatever sits behind the proxy: databases, search and payment providers. CAPEC calls the two main shapes an HTTP flood (CAPEC-488) and an HTTP DoS that keeps sessions alive (CAPEC-469).
Slow attacks keep connections busy for little cost. Slowloris opens many connections and sends header lines slowly, slow POST declares a large body and sends it a byte at a time, and slow read accepts the response slowly. Floods go the other way: a botnet sends many cheap-looking requests, often to the most expensive endpoint it can find, such as search, login or report export, spread over thousands of addresses so that no single address looks abusive.
HTTP/2 runs many request streams over one connection, and servers cap concurrent streams to bound the work. Rapid Reset (CVE-2023-44487), disclosed on 10 October 2023, sidestepped the cap: the client opens a stream and resets it at once, so the stream never counts as concurrent but the server has already started the request. Google reported a peak of 398 million requests per second, Cloudflare 201 million and AWS 155 million. Later attacks found other ways to make a server work on streams or headers the protocol considers finished or bounded.
Fixes live in the implementation, so patching is the main defense; configuration helps. nginx's fix limits new streams per event loop to twice http2_max_concurrent_streams, and its default of 1,000 requests per connection already capped the attack. Envoy 1.27.1 added premature-reset detection: by default it closes a connection once half of its streams were reset less than a second after opening, checked after 500 streams or as soon as 250 such resets add up. Node.js added streamResetBurst and streamResetRate in 22.10.0. If you cannot patch a component, disable HTTP/2 on it, or put it behind a terminator that is patched.
| Attack | Disclosed | What it abuses | Fixes to check for |
|---|---|---|---|
| Rapid Reset, CVE-2023-44487 | October 2023 | Streams reset by the client before the concurrency limit counts them | nginx 1.25.3; Envoy 1.27.1 (GHSA-jhv4-f7mr-xx76) |
| CONTINUATION flood, VU#421644 | April 2024 | Header blocks that never end, buffered or decoded without limit | Per-project CVEs: Apache httpd CVE-2024-27316, Node.js CVE-2024-27983, Go CVE-2023-45288, nghttp2 CVE-2024-28182, Envoy CVE-2024-27919 and CVE-2024-30255 |
| MadeYouReset, CVE-2025-8671 (VU#767506) | August 2025 | Malformed frames that make the server reset streams it keeps processing | Apache Tomcat CVE-2025-48989, Netty CVE-2025-55163, F5 BIG-IP CVE-2025-54500 |
| HTTP/2 bomb: HPACK amplification plus a zero flow-control window | June 2026 | Small compressed headers that expand into large allocations held open | Apache httpd 2.4.68 (CVE-2026-49975); Envoy 1.35.11, 1.36.7, 1.37.3 and 1.38.1 (CVE-2026-47774); nginx 1.29.8 and 1.30.0 add max_headers |
Each phase needs its own limit: receiving the headers, receiving the body, the whole request, sending the response and idling between keep-alive requests. Servers name and scope these timeouts differently, and the difference decides whether a slow client is stopped. We ran the same three slow clients against nginx 1.30.5, Envoy 1.39.3 and Node.js 26.3.0, configured as in the sections below, with a 10-second header limit and, where available, a 30-second request limit.
nginx logged the header timeout as a 408 with no bytes sent, and the trickled body produced no log line at all while it was open. Watch active connections, connections still reading a request, and 408 and 429 counts, not just request rates.
| Slow client | nginx 1.30.5 | Envoy 1.39.3 | Node.js 26.3.0 |
|---|---|---|---|
| Request line, then nothing | Closed at 10.0 s with no response; logged as 408 | 408 at 11.0 s (request_headers_timeout) | 408 at 10.0 s (headersTimeout) |
| One header line every 3 s | Closed at 10.0 s: the timeout covers the whole header | 408 at 11.0 s | 408 at 10.1 to 11.0 s |
| Complete headers, then one body byte every 3 s | Still open after 45 s: client_body_timeout restarts on every read | 408 at 31.0 s (request_timeout) | 408 at 30.1 to 31.0 s (requestTimeout) |
nginx has no total request timeout, so pair client_body_timeout with limit_conn, which caps how many connections one key can have open with a request in progress. In our test, 25 slow uploads from one address to a route without a request-rate limit left 20 open and refused 5 with 429. limit_conn counts a connection only once its whole header has arrived, so header trickling is bounded by client_header_timeout alone: 25 half-sent requests all stayed open until it closed them.
limit_req applies a leaky bucket per key. A burst of 60 simultaneous requests from one address to the default route got 21 responses, one plus the burst of 20, and 39 rejections with 429. On /login, 6 of 20 went through and 14 got 429. nginx returns 503 for both limits unless you set the status codes, and 503 tells clients and monitors the wrong thing. We tested this file with the TLS lines replaced by a plain-HTTP listener so the probe could connect; the TLS version passes nginx -t.
# nginx 1.30.5: limits for an internet-facing reverse proxy (tested with the probe below).
worker_processes auto;
events { worker_connections 4096; }
http {
# Bound each phase of a request.
client_header_timeout 10s; # the whole request header (default 60s)
client_body_timeout 10s; # only the gap between two body reads (default 60s)
send_timeout 10s; # the gap between two writes to the client (default 60s)
keepalive_timeout 15s; # idle keep-alive connections (default 75s)
keepalive_requests 1000; # per connection, HTTP/1.1 and HTTP/2 (default)
reset_timedout_connection on; # release the socket of a timed-out client at once
# Size limits.
client_max_body_size 1m; # default; raise it per upload route only
large_client_header_buffers 4 8k; # default
max_headers 100; # nginx 1.29.8 or later (default 1000)
# Per-client request rate and concurrent requests, answered with 429 instead of 503.
limit_req_zone $binary_remote_addr zone=per_ip:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
limit_req_status 429;
limit_conn_status 429;
upstream app {
server app:8080;
keepalive 32;
}
server {
listen 443 ssl;
http2 on;
http2_max_concurrent_streams 64; # default 128
ssl_certificate /etc/nginx/tls/server.crt;
ssl_certificate_key /etc/nginx/tls/server.key;
limit_conn conn_per_ip 20; # counts requests whose header has arrived
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 2s;
proxy_read_timeout 10s;
location / {
limit_req zone=per_ip burst=20 nodelay;
proxy_pass http://app;
}
location /upload { # limit_conn only: slow uploads are capped per client
proxy_pass http://app;
}
location = /login {
limit_req zone=login burst=5 nodelay;
proxy_pass http://app;
}
}
}Envoy's defaults suit a service mesh, not the internet, and its edge guidance lists what to change. request_headers_timeout and request_timeout are off by default, the stream idle timeout is 5 minutes, and HTTP/2 allows 1,024 concurrent streams per connection where the edge guidance recommends 100. The configuration below passed envoy --mode validate and answered our slow clients with 408, rejected a header containing an underscore with 400, and let 102 of a 300-request burst through its local token bucket.
The local rate limiter here is one bucket for the whole Envoy instance, not one per client; use descriptors or the global rate limit service to key it on client, route or API key. The overload manager stops accepting requests near the heap limit and caps active connections across all listeners, which keeps Envoy alive long enough to shed load rather than crash.
overload_manager:
refresh_interval: 0.25s
resource_monitors:
- name: envoy.resource_monitors.global_downstream_max_connections
typed_config:
"@type": type.googleapis.com/envoy.extensions.resource_monitors.downstream_connections.v3.DownstreamConnectionsConfig
max_active_downstream_connections: 50000
- name: envoy.resource_monitors.fixed_heap
typed_config:
"@type": type.googleapis.com/envoy.extensions.resource_monitors.fixed_heap.v3.FixedHeapConfig
max_heap_size_bytes: 1073741824 # 1 GiB: tune to the container limit
actions:
- name: envoy.overload_actions.stop_accepting_requests
triggers:
- name: envoy.resource_monitors.fixed_heap
threshold: {value: 0.95}
static_resources:
listeners:
- name: ingress
address: {socket_address: {address: 0.0.0.0, port_value: 10000}}
per_connection_buffer_limit_bytes: 32768
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
use_remote_address: true
request_headers_timeout: 10s # whole header block; off by default
request_timeout: 30s # whole request; off by default
stream_idle_timeout: 60s # default 5 min
common_http_protocol_options:
idle_timeout: 300s
max_headers_count: 100
headers_with_underscores_action: REJECT_REQUEST
http2_protocol_options:
max_concurrent_streams: 100
initial_stream_window_size: 65536
initial_connection_window_size: 1048576
route_config:
virtual_hosts:
- name: app
domains: ["*"]
routes:
- match: {prefix: "/"}
route: {cluster: app, timeout: 15s}
http_filters:
- name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
token_bucket:
max_tokens: 100
tokens_per_fill: 100
fill_interval: 1s
filter_enabled:
runtime_key: local_rate_limit_enabled
default_value: {numerator: 100, denominator: HUNDRED}
filter_enforced:
runtime_key: local_rate_limit_enforced
default_value: {numerator: 100, denominator: HUNDRED}
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: app
type: STRICT_DNS
per_connection_buffer_limit_bytes: 32768
load_assignment:
cluster_name: app
endpoints:
- lb_endpoints:
- endpoint: {address: {socket_address: {address: app, port_value: 8080}}}Node's HTTP server enforces headersTimeout (60 seconds) and requestTimeout (300 seconds) by default, but checks them only every connectionsCheckingInterval, 30 seconds by default, so a deadline can be overshot by up to that interval. Lower all three for a server that faces clients directly. For HTTP/2, set maxConcurrentStreams: Node sets no default limit, which in theory allows 2^32-1 concurrent streams per session.
In front of a Node service, a proxy should still terminate client connections and enforce the same limits, but these settings keep the process safe when someone reaches it directly or the proxy is misconfigured.
import http from 'node:http';
import http2 from 'node:http2';
import {readFileSync} from 'node:fs';
const limits = {
headersTimeout: 10_000, // whole header block (default 60 s)
requestTimeout: 30_000, // whole request, headers and body (default 300 s)
keepAliveTimeout: 5_000, // idle keep-alive socket (default 5 s)
connectionsCheckingInterval: 1_000, // how often the two deadlines above are checked (default 30 s)
maxHeaderSize: 16_384, // bytes (default 16 KiB)
};
function handler(req, res) {
let size = 0;
req.on('data', chunk => {
size += chunk.length;
if (size > 1_048_576) req.destroy(); // 1 MiB body cap
});
req.on('end', () => res.end('ok\n'));
}
const server = http.createServer(limits, handler);
server.maxHeadersCount = 100; // default 1000 (2000 in the Node.js 24 docs)
server.maxRequestsPerSocket = 1000; // default 0, unlimited
server.listen(8080);
// HTTP/2 over TLS: cap concurrent streams; Node sets no limit by default.
const h2 = http2.createSecureServer(
{
key: readFileSync('tls/server.key'),
cert: readFileSync('tls/server.crt'),
settings: {maxConcurrentStreams: 100},
maxSessionInvalidFrames: 1000, // default
maxSessionRejectedStreams: 100, // default
},
handler,
);
h2.listen(8443);A CDN or cloud WAF spreads a flood across a network much larger than yours and can rate limit before traffic reaches you. Rate-based rules differ by provider. AWS WAF counts over a 60, 120, 300 or 600-second window with a minimum limit of 10 and can aggregate by IP, forwarded IP or custom keys. Google Cloud Armor can throttle or ban a client, keyed on IP, header, cookie, path, TLS fingerprint and more. Azure Front Door counts per socket IP over one or five minutes and can let some requests over a low threshold through, because counters are kept per edge server.
The edge protects nothing if attackers can reach the origin directly. Accept origin traffic only from the CDN, through provider IP lists, a private connection or an authenticated origin header, and trust X-Forwarded-For only from the CDN; the API gateway inventory guide at /domains/runtime/api-security/api-gateway-inventory/ covers trusted proxies. Rate limit on the identity that costs you money, not only on IP addresses: per session, per API key and per tenant on expensive routes, as the API resource limits guide at /domains/runtime/api-security/resource-consumption-limits/ shows.
When demand exceeds capacity, every request slowing down is the worst outcome: clients time out, retry and multiply the load. Refuse some requests quickly instead. Put a concurrency limit in front of each expensive dependency, reject excess work with 503 and a Retry-After header (RFC 9110) or 429 for per-client limits (RFC 6585), and make clients retry with exponential backoff and jitter. Google's SRE book describes this in its chapters on handling overload and cascading failures.
Decide in advance what to keep: health checks, logins of existing users, checkout, paying tenants. Then serve the rest from cache, degrade it, or refuse it. Cache public responses at the edge so a flood of identical requests never reaches the origin, and move expensive work such as exports and reports to bounded queues, as the bounded background work guide at /domains/runtime/availability/bounded-background-work/ describes.
The probe below opens one HTTP/1.1 connection and sends a request a piece at a time, which is how we produced the results above. Point it at a staging listener, not at production, and not through a provider's network without following its testing policy. For HTTP/2, use a load generator that supports it, such as h2load, and watch memory, active streams and connection counts on the server.
// slow-client.mjs: send an HTTP/1.1 request a piece at a time and report how the server ends it.
// node slow-client.mjs <host> <port> header|body [interval-ms]
import net from 'node:net';
const [host, port, mode = 'header', interval = '3000'] = process.argv.slice(2);
const pieces =
mode === 'header'
? ['GET / HTTP/1.1\r\nHost: test\r\n', ...Array.from({length: 40}, (_, i) => `X-Pad-${i}: a\r\n`)]
: ['POST /upload HTTP/1.1\r\nHost: test\r\nContent-Length: 1000\r\n\r\n', ...Array(40).fill('a')];
const started = Date.now();
const socket = net.connect(Number(port), host);
let reply = '';
let sent = 0;
const timer = setInterval(() => sent < pieces.length && socket.write(pieces[sent++]), Number(interval));
socket.on('connect', () => socket.write(pieces[sent++]));
socket.on('data', chunk => (reply += chunk));
socket.on('close', () => {
clearInterval(timer);
const seconds = ((Date.now() - started) / 1000).toFixed(1);
console.log(`closed after ${seconds}s, ${sent} pieces sent, reply: ${reply.split('\r\n')[0] || '(none)'}`);
});
setTimeout(() => {
console.log(`still open after 45s, ${sent} pieces sent`);
socket.destroy();
}, 45_000).unref();- Trusting a per-read body timeout to stop slow POST.
- Rate limiting on the client IP header without stripping it at the edge, so any client can choose its own key.
- Answering limits with 503, which monitors read as an outage and clients retry at once.
- Raising
keepalive_requestsor HTTP/2 stream limits for benchmarks and leaving them raised. - An origin that answers anyone who finds its address, which makes the CDN optional.
- Retries without backoff in your own clients and services, which turn a partial outage into a full one.
Guided practice
A public API behind nginx slows down for everyone. Use the observations below to decide what is happening and which changes to make, in order.
Supplied scenario material
Observed over 10 minutes (nginx 1.30.5, HTTP/1.1 and HTTP/2 enabled, no CDN)
Active connections: 180 normally, now 9,400; 8,900 in the "reading" state
Requests per second: 1,200 normally, now 1,350
Upstream (Node.js) CPU: 35%; database CPU: 40%
Top client addresses: 2,700 distinct; none above 0.5% of connections
Sample from a packet capture: POST /v1/uploads with Content-Length: 104857600, one byte every 8 seconds
nginx configuration today
client_header_timeout 60s; client_body_timeout 60s; client_max_body_size 100m;
no limit_conn; limit_req zone=per_ip rate=50r/s on /v1/search only
Origin address: published in DNS; the API also accepts traffic directly on its public IP1. Name the attack
Explain why requests per second barely changed while the service slowed down.
Expected result and reasoning
It is a slow POST attack: thousands of connections sit in the reading state with complete headers, sending a body a byte every 8 seconds. That is under the 60-second
client_body_timeout, which only measures the gap between reads, so no timeout fires and connections, not requests, run out.2. Choose immediate changes
Pick the nginx changes that relieve this attack today.
Expected result and reasoning
Cut
client_max_body_sizeto what uploads need and give the upload route its own lower limit. Addlimit_connper address with a status of 429, lowerclient_body_timeout, and enablereset_timedout_connection. If uploads must stay large, move them to direct-to-storage uploads with pre-signed URLs.3. Account for the spread
Why might a per-address connection limit not be enough here?
Expected result and reasoning
The traffic comes from 2,700 addresses with none above 0.5%, so a per-address limit of a few connections still lets thousands through. Add a CDN or proxy that buffers request bodies before they reach the origin, require authentication on the upload route so limits can key on the account, and cap total concurrent uploads.
4. Close the bypass
What has to change about the origin?
Expected result and reasoning
Put the API behind a CDN or cloud WAF and accept traffic only from it: provider IP ranges or a private connection, and an origin authentication header. Otherwise attackers keep using the published IP.
5. Prove the fix
Describe the test that shows the change works.
Expected result and reasoning
In staging, run the slow-body probe with more connections than the limit and confirm the extras get 429 and the rest close within the configured bound. Confirm that normal uploads still succeed, and that a direct request to the origin is refused.
The incident data is supplied; the nginx, Envoy and Node.js behavior on this page was measured with local containers and a single client machine, not a distributed attack.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- CAPEC-469: HTTP DoS. CAPEC version 3.9.
- nginx: ngx_http_core_module
- nginx: ngx_http_limit_req_module
- F5 NGINX: HTTP/2 Rapid Reset attack impacting F5 NGINX products. 10 October 2023.
- nginx: CHANGES (1.25.3 HTTP/2 client detection, 1.29.8 max_headers)
- Google Cloud: mitigating a DDoS attack peaking above 398 million rps. 10 October 2023.
- Cloudflare: HTTP/2 Rapid Reset technical breakdown. 10 October 2023.
- CERT/CC VU#421644: HTTP/2 CONTINUATION frames. 3 April 2024.
- CERT/CC VU#767506: MadeYouReset. 13 August 2025.
- Calif: Codex discovered a hidden HTTP/2 bomb. 2 June 2026.
- Apache httpd 2.4 vulnerabilities (CVE-2026-49975)
- Envoy GHSA-22m2-hvr2-xqc8 (CVE-2026-47774). 3 June 2026.
- Envoy: configuring Envoy as an edge proxy
- Node.js 24 HTTP server. Node.js v24.21.0.
- AWS WAF: rate-based rule settings
- Google Cloud Armor: rate limiting overview
- Azure Front Door WAF rate limiting
- Google SRE book, chapter 21: handling overload