Quick answer
CVE-2023-44487, known as HTTP/2 Rapid Reset, is a protocol-level denial-of-service technique disclosed on 10 October 2023. A client opens an HTTP/2 stream with a request and immediately sends RST_STREAM to cancel it. Cancelled streams stop counting toward the concurrency limit, so one connection can keep the server busy far faster than it can shed the work. It powered record application-layer DDoS attacks, including 201 million and 398 million requests per second, and is listed in the CISA KEV catalogue.
At a glance
high severity- Identifier
- CVE-2023-44487
- Also known as
- HTTP/2 Rapid Reset
- Product
- HTTP/2 protocol — most server and proxy implementations
- Affected
- nginx 1.9.5 – 1.25.2 (mainly where keepalive_requests was raised far above the default); Go net/http before 1.21.3 / 1.20.10; golang.org/x/net/http2 before 0.17.0; Envoy before 1.27.1 / 1.26.5 / 1.25.10 / 1.24.10; Node.js (nghttp2) and many others — see vendor advisories
- Fixed in
- nginx 1.25.3; NGINX Plus R29p1 / R30p1; Go 1.21.3 / 1.20.10; x/net 0.17.0; Envoy 1.27.1 / 1.26.5 / 1.25.10 / 1.24.10; Node.js 18.18.2 / 20.8.1; nghttp2 1.57.0
- CVSS
- 7.5 (CVSS 3.1)
- Disclosed
- Known exploited (CISA KEV)
- Yes
Exploited in the wild since August 2023. Listed in CISA KEV (added 10 October 2023). Patched across the ecosystem.
01What is HTTP/2 Rapid Reset?
CVE-2023-44487 is a denial-of-service weakness in the HTTP/2 protocol itself, not a bug in one product. It was disclosed on 10 October 2023 by Cloudflare, Google and AWS after being exploited in the wild from August 2023. CISA added it to the Known Exploited Vulnerabilities catalogue the same day.
The mechanism is simple. HTTP/2 lets a client cancel a request by sending an RST_STREAM frame. The attacker opens a stream with a complete request and immediately cancels it, over and over. Each cancellation frees a slot for a new stream instantly, so the request rate on one connection is bounded only by the attacker's bandwidth — not by the round-trip time and not by the server's concurrency limit.
Because it is an application-layer flood that consumes CPU rather than bandwidth, it belongs to the same family as a classic HTTP flood but is far more efficient. For the wider picture of volumetric versus application attacks, see what a DDoS attack is.
02Why did it break records?
HTTP/2 multiplexes many streams over one connection and caps how many can be active at once using SETTINGS_MAX_CONCURRENT_STREAMS, typically 100 or 128. In a normal flood, that cap plus the round-trip time limits how many requests a client can keep in flight. Rapid Reset removes both limits.
When a stream is reset it immediately becomes closed, and closed streams do not count toward the concurrency cap. So the attacker never exceeds the advertised limit, yet keeps the server perpetually busy setting up and tearing down streams: allocating stream state, decompressing headers, parsing the request, and often proxying it to a backend before the cancellation is even processed. The client pays almost nothing; the server pays for every request. That asymmetry is what made it an amplifier at the application layer.
| Reporter | Peak rate | Context |
|---|---|---|
| Cloudflare | 201 million req/s | Nearly 3x Cloudflare's previous record of 71M req/s; botnet of about 20,000 machines |
| 398 million req/s | Largest reported Layer 7 attack at the time | |
| AWS | 155 million req/s | A dozen-plus events over 28–29 August 2023 |
03How does the attack work, step by step?
At a conceptual level, the loop is: open a stream with a full request, then cancel it before the response comes back. There are two documented variants beyond the basic form, which is why counting client resets alone was not a complete defence.
The basic form abuses request cancellation, a legitimate feature browsers use when a user navigates away. The non-cancelling variant does not reset anything; it simply opens more streams than allowed, which is why servers also had to close connections that exceed the stream limit rather than only limiting resets.
- Basic Rapid Reset: send HEADERS (with END_STREAM) then an immediate RST_STREAM, repeatedly
- Batched variant: open a large batch of streams, wait briefly, cancel them all, then open another batch
- Non-cancelling variant: open more concurrent streams than the server advertised, without resetting at all
- In every case the server does real work for requests that are never completed from the client's side
04Which servers patched how?
Because it is a protocol weakness, almost every HTTP/2 implementation shipped a fix within days or weeks of the 10 October 2023 disclosure. The fixes share a theme: account for streams that are reset or cancelled, and close the whole connection when a client's behaviour looks abusive, rather than trying to block individual requests.
F5 noted that nginx with default settings was largely protected already, because the default keepalive_requests limit of 1,000 caps how many requests a single connection can make; the patch tightened stream-rate handling further. The lesson across vendors is that the fix is operational, not a single magic value; the other nginx limits that matter in a flood are covered in nginx DDoS protection.
| Implementation | Fixed version(s) | Approach |
|---|---|---|
| nginx | 1.25.3; NGINX Plus R29p1 / R30p1 | Limits new streams per event loop to 2x http2_max_concurrent_streams; keep keepalive_requests at its default |
| Go net/http, golang.org/x/net/http2 | go1.21.3, go1.20.10; x/net 0.17.0 (CVE-2023-39325) | Caps concurrent handler goroutines; queues and then drops connections with oversized queues |
| Envoy | 1.27.1, 1.26.5, 1.25.10, 1.24.10 | Budget on premature resets per connection (default 500) before disconnecting |
| Node.js (nghttp2) | 18.18.2, 20.8.1; nghttp2 1.57.0 | Bounds rapid stream create/cancel cycles |
| Apache Tomcat | 8.5.94, 9.0.81, 10.1.14, 11.0.0-M12 | Treats excess stream churn as abuse |
| Apache httpd | 2.4.58 (related CVE-2023-45802) | Reclaims stream memory promptly on RST |
| Netty / Jetty | netty 4.1.100.Final; jetty 9.4.53 / 10.0.17 / 11.0.17 / 12.0.2 | Reset accounting and connection teardown |
05What stops it, and what does not?
The vendor guidance converged on one principle: you cannot usefully block individual requests, because by the time you decide a request is abusive the stream is already closed. The whole TCP connection has to be torn down when abuse is detected.
- Track per-connection statistics: a connection with many requests of which a large share are cancelled is a strong candidate for teardown (Google's example: more than 100 requests with over 50% cancelled)
- Close connections that exceed the stream limit, to cover the non-cancelling variant
- Keep server stream limits and keepalive request caps at sane defaults
- Apply rate limiting and connection limits as supporting controls, not as the sole defence
- Do not rely on GOAWAY alone — it does not stop inbound requests fast enough
06How do you detect it in logs?
The fingerprint is a high rate of requests that never complete normally, concentrated on a small number of connections, with CPU high but bandwidth ordinary. In nginx access logs, HTTP/2 requests the client cancelled typically surface as status 499 (client closed the request), which Cloudflare also observed during the attacks. A per-connection count of 499s is a useful triage signal.
Treat the threshold below as illustrative and tune it to your traffic; a few cancellations per connection are normal browser behaviour. Envoy's advisory describes the same signal as elevated downstream_rq_http2_total without a substantial rise in downstream_cx_active. For the step-by-step response during an incident, see how to stop a DDoS attack.
# log format must include $connection and $server_protocol, e.g.:
# log_format h2 '$remote_addr conn=$connection $server_protocol $status "$request"';
# fields: $1 address, $2 conn=N, $3 protocol, $4 status
# connections with more than 100 cancelled HTTP/2 requests
awk '$3=="HTTP/2.0" && $4==499 {n[$1" "$2]++}
END {for (k in n) if (n[k]>100) print n[k], k}' access.logQuestions
Frequently asked questions
Sources
Sources and verification
The log_format line loaded into nginx 1.22.1 with nginx -t, and the per-connection 499 count run with mawk and GNU awk 5.2.1 against a sample access log, on Debian 12, October 2026.
- 01NVD — CVE-2023-44487 · NVD
- 02CISA alert — HTTP/2 Rapid Reset, CVE-2023-44487 · CISA
- 03Cloudflare — technical breakdown of the HTTP/2 Rapid Reset DDoS attack · Cloudflare
- 04Google Cloud — how the novel HTTP/2 Rapid Reset DDoS attack works · Google Cloud
- 05F5 NGINX — HTTP/2 Rapid Reset attack impacting NGINX products · F5 NGINX
- 06Go vulnerability report GO-2023-2102 (CVE-2023-39325) · Go
- 07Envoy security advisory GHSA-jhv4-f7mr-xx76 · Envoy
- 08AWS — How AWS protects customers from DDoS events · AWS Security Blog
- 09Apache Tomcat 9 security vulnerabilities · Apache Tomcat
- 10Apache HTTP Server 2.4 vulnerabilities (CVE-2023-45802) · Apache HTTP Server
Last updated