The job of a CDN is to make sure most requests never reach your origin. A GoldenEye DDoS attack is built to make sure every one of them does. It appends a few randomised query-string parameters to each request, enough to make each one a brand new object as far as the cache is concerned. A cache that has never seen a URL has nothing to serve, so it goes to the origin and asks.
That changes who is being tested. The edge keeps handling the traffic and keeps reporting healthy, while behind it your application servers render every page from scratch and your database answers every query, at a rate you sized assuming the cache would absorb most of it. Your CDN is working exactly as configured. It is just configured to read the attack as a very large number of visitors asking for very different things.
The short version
- It attacks your cache key, not your bandwidth. Two to five randomised parameters per request turn a hit into a miss, and a miss is a full origin round trip.
- The damage lands deep in the stack. A slow attack stalls at your web server. This one reaches your application code and your database, because that is what a miss costs.
- Whether it works is a config question with two opposite answers. nginx and Cloudflare put the query string in the cache key by default, so every busted URL is a miss. CloudFront’s CachingOptimized policy includes none, so the same URLs share one key.
- Every request looks like a browser. A different real-world
User-Agenteach time, withDNTandUpgrade-Insecure-Requestson roughly half. A rule that blocks one bad user agent has nothing to hold on to. - Detection inverts. Every request is logged, so evidence is not the problem. Cache hit ratio names it, and that is rarely on the wall.
This is one post in a series covering each of the ten simulation types Obsidio runs, one per attack. They fail in genuinely different places, which is why a test programme needs coverage across the categories rather than volume in one. For how they fit together and how to structure a first run, start with our guide on how to test your DDoS protection.
The ten simulation types
One post each, grouped the way the platform groups them.
Connection exhaustion
Protocol-specific and scripted
- DNS Floodcoming soon
- Puppeteer Scriptfunctional load, not an attack
What a GoldenEye DDoS attack actually does
Obsidio’s GoldenEye simulation is a parallel-pool HTTP GET flood with one addition: before each request goes out, the target URL gets a fresh set of randomised query-string parameters. There are two to five of them, drawn from names that read as ordinary in a log file (cb, v, t, nocache and similar) plus two search-style keys such as q, page, ref or utm_source.
Each request then gets a header overlay. With useRandomHeaders on, which is the default, the User-Agent is drawn per request from a pool of eight real desktop and mobile browser strings. DNT: 1 and Upgrade-Insecure-Requests: 1 are each added to roughly half of them, probabilistically, the way a mixed population of browsers behaves. A fixed set rides along on all of them: the Host, ordinary Accept values, and Cache-Control: no-cache.
The header does less than it looks. A no-cache request is only a preference, in RFC 9111’s own words: it “indicates that the client prefers a stored response not be used to satisfy the request without successful validation on the origin server.” A cache may decline. The query string is different, because RFC 9111 builds the cache key from at least the request method and the target URI, and the URI carries the query. Changing it does not ask the cache to skip its copy; it asks for a resource the cache has no copy of.
In plain terms: an HTTP flood where every visitor wears a different disguise and tweaks the order number on the way in, so the shop can never say “I already filled that one, here is the copy.”
Which of your controls should have caught this
Walk the whole chain, because the answer is rarely the control people expect. Three groups matter: what sits in front, what absorbs what gets through, and whether anything tells you.
Zone 1
Should any of these have stopped it?
- CDN and edge cache. The whole attack aims here, and one setting decides the run. nginx defaults
proxy_cache_keyto$scheme$proxy_host$request_uri, which carries the query string, so every busted URL is a guaranteed miss. Cloudflare’s default key includes the query string too. CloudFront’s managed CachingOptimized policy includes none, so the same traffic collapses onto one entry and never leaves the edge. Nobody can tell from outside which you are. - DDoS protection and scrubbing. Real volume this time, so a rate trigger has something to fire on. Whether it fires in time is the question.
- Rate limiting. Thresholds apply, but a limiter keyed on the full URL sees every request as a different resource.
- Bot detection and challenges. Rotation across eight genuine browser strings reduces header-matching rules to noise. Challenges fare better.
- Firewall and WAF. Sees well-formed GET requests with nothing malicious in them, so there is no signature to match.
Zone 2
What takes the hit?
- Load balancer or reverse proxy. If it caches, it splits on the same key logic and inherits the answer.
- Connection layer. Handshake churn on top of requests, since a fresh socket opens per request by default.
- Web server. Busy, but rarely the constraint: requests complete and free their worker.
- Application and database. Where it bites. Every miss is a full render: templates, sessions, queries. Capacity sized for cache-assisted traffic now serves all of it.
- Auto-scaling. Sees real load and scales, which is both the intended behaviour and the invoice. Work out what an hour of that costs before you need to.
Zone 3
Would you find out?
- Dashboards. Requests per second climb, so there is something on the graph. Cache hit ratio names it, and that is two clicks away.
- A check from outside your network. Edge response times can look respectable while origin requests queue behind them.
- Logging and your SIEM. Everything lands in the access log. Grouping it is the hard part: every hit has a new user agent and an unseen URL.
- Alerting. Depends on whether anything watches origin request rate or cache hit ratio, not just edge volume.
- On-call. Somebody is paged if the alert exists. Whether the runbook mentions a cache-bypassing flood is worth reading first.
Purple marks what only a test on your own infrastructure can settle. Everything else follows from how the attack works, not from how well your team configured things.
The last group is the part teams discover late. A control that works but is invisible is still a finding.
What separates it from a plain HTTP flood
An HTTP flood attack and a GoldenEye run look alike on a bandwidth graph and behave nothing alike. HTTP Flood sends the configured method, URL and body exactly as given, with no randomisation, so an edge that caches the target URL absorbs it. GoldenEye differs in two places, and both matter for layer 7 DDoS mitigation: the cache-buster makes origin reach a property of the request, and the header randomisation leaves shape-based filters less to work with. The parameters are short:
parallelRequestsPerWorker, default 20, range 1 to 100. Concurrent loops per worker, roughly linear in cache-busted requests per second. The platform estimates five per second per loop, assuming the target answers within 200 ms.useRandomHeaders, default true. On, a random browserUser-Agentper request from the pool of eight. Off, everything is pinned toGoldenEye/1.0and trivially fingerprintable, which is the point of the switch: use it to check whether your header-based rules fire at all.headers, default empty. Merged with the fixed set and the overlays; anything you set pins its slot, so supplyingDNTreplaces the probabilistic overlay.
Then there is keepAlive, false here and true for HTTP Flood, and that default is worth understanding. HTTP Flood pools sockets so the handshake is amortised and load concentrates on application threads and the database. GoldenEye has no such need, because cache-busted URLs reach the origin whether or not the socket is reused. Pooling buys nothing the attack did not already have, so paying the handshake every time folds connection setup into the load instead. Switch it on and origin reach survives; you stop stressing the handshake path.
How to defend against a GoldenEye DDoS attack
You cannot block the cache-buster, because you cannot enumerate names somebody might invent. The work is deciding which parameters may influence your cache key, and making sure the origin survives the rest.
- Normalise the cache key. On Cloudflare, the Ignore Query String cache level drops query strings from the key and is available on every plan, including Free; per-parameter control lives in Cache Rules and is Enterprise. On CloudFront it is a cache policy choice: managed CachingOptimized includes no query strings and no cookies. On nginx,
proxy_cache_keydefaults to$scheme$proxy_host$request_uri; dropping the query means building it from$uri. - Allowlist the parameters rather than blocklisting them. Real traffic on a path uses a small, knowable set, and excluding the rest bounds the surface. Cloudflare caps a custom cache key at 100 parameters. The cost is correctness: two different responses can then share one entry, which breaks search, pagination and anything personalised. Do it per path, and enable Normalize URLs to origin alongside it, as Cloudflare’s guidance says, to avoid cache poisoning.
- Bot management that does not depend on a header. A rule matching one user agent string is defeated by a pool of eight; challenges and behavioural signals survive rotation. This matters most where caching cannot help: Cloudflare does not cache HTML or JSON by default, so a flood at a dynamic page reaches origin whatever your key says.
- Rate limit at the origin on identity, not on the URL. The URL is unique every time, so anything keyed on it counts to one. Key on source, session or normalised path, and keep the origin unreachable directly, because an exposed IP routes around every control above.
What you can check yourself, and what it will get wrong
Read the cache configuration, then send yourself a request carrying a junk parameter and read the cache status your CDN reports back. That is the cheapest useful hour here: it tells you what your key is, path by path. It does not tell you what your origin does when that key turns every request into a miss under concurrency.
Test alert routing on its own. A synthetic alert verifies the paging plumbing with no attack traffic.
Run a single-source test against staging, with change-management sign-off, on infrastructure you own. Legitimate, useful, and exactly where the trap sits.
A test from one machine does not merely fail to answer the Zone 1 questions. It answers several of them wrongly, in a reassuring direction. Your per-IP rate limit catches a single source in seconds, bot detection flags one client hammering odd URLs, and reputation filtering handles the address. Every control in the front zone appears to work, you write “mitigated” in the notes, and the finding you needed is the one you did not get.
Then the real thing arrives from thousands of residential addresses, none of which crosses a threshold on its own, and those controls behave differently, because they key on source concentration and traffic realism. One machine reproduces neither, and it cannot ask the question that decides the incident loudly enough: whether your origin holds when the cache hit ratio goes to nothing.
How Obsidio tests a cache-bypassing flood
Obsidio runs GoldenEye as a controlled, authorized simulation against infrastructure you own. Domain ownership is verified by DNS TXT record before any traffic flows, runs ramp up gradually, and any run can be aborted live. You choose the concurrency per worker, whether headers randomise and whether connections are reused. Because load comes from 100,000+ globally distributed real devices rather than a handful of datacenter instances, per-IP limiting and reputation filtering meet the source spread a real attack has.
Two metrics matter more than the rest:
- Cache hit ratio. If it falls off a cliff when the run starts, the cache-buster is working and your origin is serving everything. If it holds, your key is normalising the parameters away and the edge is absorbing the attack, which is worth having on paper.
- Origin request rate. The same story measured where it lands. Compare it with what your application tier is provisioned for, and watch database pool usage and query latency beside it.
Plan for the detection questions too. Obsidio can report how your edge responded and what reached it, because that is measurable from outside. Whether your cache analytics showed the drop, whether your SIEM correlated anything and whether anyone was paged are answers only your own consoles hold, so have someone watching them during the window.
Strength is proven, not promised. A high cache hit ratio protects you right up until someone sends a URL you have never seen before.
Where this fits in a testing programme
GoldenEye sits in the application-layer flood category, alongside HTTP Flood, Browser Flood and Apache Killer. Run it back to back with HTTP Flood and you isolate one variable: if HTTP Flood is absorbed and GoldenEye is not, the difference is your cache key. Slowloris inverts almost everything here: no volume for the front defenses to see, damage that stops at the connection pool, and silence instead of too much noise. That is the argument for coverage across the ten simulation types instead of volume in one.
For regulated institutions the test also has to leave a record. Obsidio produces cryptographically attested, tamper-evident reports mapped to FINMA, DORA and NIS2, generated inside Trusted Execution Environments, so the outcome of a run is an artifact you can file rather than a screenshot you have to vouch for.
To scope an authorized GoldenEye simulation against your own infrastructure, see the Obsidio platform or get in touch with the team. Independent, neutral, verifiable: resilience you can prove.
