Skip to main content

In mid-to-late June 2026, Read the Docs absorbed the largest attack in its history: over 5.5 million requests per minute, roughly a hundred times its normal peak, sustained across nearly ten days. The team published the postmortem on 8 September. Read as an incident report it is unremarkable in the good way, a competent ops team doing competent work under pressure. Read as a design document it is far less comfortable, because the attackers did not go anywhere near the things Read the Docs had hardened. They went after the responses nobody had ever made a decision about: 404s, temporary redirects, and everything else the cache had quietly been told to skip.

TL;DR

  • Read the Docs was hit with 5.5 million requests per minute for nearly ten days in June 2026, around 100x its normal peak traffic.
  • The attack deliberately targeted uncached responses, mainly 404s and 302 redirects, because those are the only requests that actually reach your origin servers.
  • IP blocking, ASN limits and TLS fingerprinting all degraded simultaneously: traffic came from millions of residential IPs with randomised TLS parameters over HTTP/2 and HTTP/3.
  • The “yo-yo” pattern (ramp up, back off, repeat) is designed to inflate autoscaling bills rather than cause a clean outage, so availability dashboards stay green throughout.
  • Your DDoS posture is set at design time by the cache policy on your non-200 responses, not at incident time by WAF rules.

Ten days, and none of it aimed at the front door

The normal daily peak at Read the Docs is under 100,000 requests per minute. During the attack the requests arrived from millions of unique source addresses across hundreds of networks, including residential IP blocks as well as major and minor hosting providers. Requests from every country at once.

The attack did not open on the documentation Read the Docs serves to hundreds of thousands of readers. It opened on a small number of domains where the attackers had found temporary redirects that were not cached at the edge and were being served by the Python backend rather than by Nginx. Within minutes the ops team was paged for a short outage. Within half an hour the redirects had been moved to the edge. The attackers then spent another week and a half working through alternatives: 404s on unique non-existent paths, the commercially hosted docs, and the author-facing dashboards that sit behind a login.

Every control you own degrades at the same moment

Your defences here do not fail in sequence, giving you time to fall back. They fail together. IP-based rate limiting goes first. When traffic arrives from millions of addresses, a large share of them residential, a per-IP limit describes nothing useful. Geographic and ASN rules go next, because the traffic came from everywhere simultaneously, which is particularly awkward when limits are applied per CDN point of presence and when a meaningful slice of your legitimate traffic is cloud-hosted bots you want to keep. Client fingerprinting is third: a JA4 hash identifies a tool rather than a user, which is genuinely useful right up to the moment an attacker randomises their TLS parameters, as this one did.

This is a different animal from the single-connection resource exhaustion we covered in the HTTP/2 Bomb, where one crafted connection could exhaust 32GB of RAM on an unpatched server. That had a patch. This does not. Once identity-based filtering is gone, the only lever left is what a request costs you to answer, and that was decided months before the attack started.

The surface nobody specified

On any project of reasonable maturity, somebody has made a deliberate decision about caching the things that matter. Product pages, article bodies, API responses, static assets with content-hashed filenames. Those decisions get reviewed, they get a TTL, they occasionally get a diagram.

Almost nobody makes a decision about the 404. Or the 302 a marketing campaign added eighteen months ago. Or the search endpoint whose query string sits in the cache key and therefore has no cache at all. Or the logged-in dashboard. Those responses inherit whatever the framework and the CDN default to, and the framework default is nearly always “do not cache”, because that is the right default for correctness and a disastrous one for cost.

The attacker’s discovery method is embarrassingly cheap. Send requests, watch the response times. Fast means cached and therefore worthless to them. Slow means origin, and origin is the target. They are enumerating the part of your application that nobody specified, and they can enumerate it faster than you can audit it. Read the Docs says so plainly: months on, it is still finding paths and endpoints that were not cached.

So your attack surface is not the list of things you built. It is the list of responses nobody wrote a cache policy for. On most sites those two lists barely overlap, and the second one has never appeared in a sprint. We have written about cache invalidation as an optimisation problem; this is the same header read as a security control.

The first symptom is an invoice

The other detail worth stealing is the yo-yo. Attackers ramped up until they located a rate limit threshold, backed off long enough for the window to expire, then ramped again. The stated purpose is to maximise the financial cost of autoscaled infrastructure while producing intermittent degradation rather than a clean, obvious outage.

Consider what that does to your instrumentation. Uptime checks pass, because the site is up most of the time. Error-rate alerts stay quiet, because the traffic is calibrated to sit under the threshold that would trip them. The autoscaler does precisely what you paid it to do and adds capacity. The only thing that has genuinely moved is the bill, and you will see the bill in thirty days.

This is now the third time we have written a version of the same sentence. Absence of a signal on your dashboard is not evidence of absence. It was true of visibility inside AI answer surfaces, and true again of pages that are indexed but never shown. An attack that spends your money instead of your uptime is invisible to every instrument you bought to watch uptime.

Five things worth doing this quarter

1. Write a cache policy for the responses you never think about. 404s, 301s, 302s, error pages, maintenance pages. Even sixty seconds of Cache-Control takes them off the menu entirely. The defaults in your CDN and your web framework are not the settings you want.

2. Inventory your cache-miss surface before somebody else does. List every response that reaches origin: search, dynamic redirects, anything with a user-specific query string, authenticated dashboards and webhook receivers. For each one, either cache it briefly or push the work to the edge. Whatever survives both options is your genuine exposure, and at least you now know the number.

3. Alert on origin requests per minute, not only on errors and latency. The metric that catches a yo-yo attack early is uncached requests arriving at your servers, and Read the Docs alerts on exactly that, with a Slack notification once it passes 45,000 a minute. Pair it with a hard ceiling on your autoscaler. A maximum instance count is a financial control, and an unbounded autoscaler is a blank cheque made out to whoever finds your 404 handler first.

4. Put the edge rules in version control now, not during the incident. Read the Docs manages dozens of rate limiting rules through Terraform, and names infrastructure as code as the thing that let them review, test and roll out complex filtering safely while under attack. Rules typed into a vendor dashboard at three in the morning are unreviewable and impossible to unwind afterwards.

5. Agree the friction budget with the client before you need it. Switching on a blanket JavaScript challenge is one click, and it breaks every API integration and automated consumer you have. That is a commercial decision, not an operational one. Settle in advance who signs it off and what the escape hatch is for real users. Read the Docs chose to let some malicious traffic through rather than challenge everybody, which was a judgement about their audience rather than their infrastructure.

Before June, not during it

The line in the postmortem that should concern any agency is its last one: as AI tooling and proxy networks make attacks like this cheaper and more accessible, they stop being reserved for large enterprise targets and become the baseline reality for any high-profile public service. Read the Docs is a documentation host. Its defence held because the decisions that mattered had already been made, in cache headers and Terraform files, before anything happened.

Cache policy, edge rules and autoscaler ceilings are the sort of thing our DevOps practice would rather review while nothing is going wrong. If you want a second pair of eyes on your cache-miss surface, get in touch.

📷 Photo by Vikas Kumar on Unsplash