Most defenses against a layer 7 DDoS attack work by finding something a real browser would never do. A missing header. A TLS fingerprint no shipping Chrome produces. A client that asks for the HTML and then never fetches the stylesheet. Find the gap and you have something to act on.
A Browser Flood closes that gap. It drives a real Chromium tab that loads your page, parses the HTML, fetches every stylesheet, script and image the page asks for, runs your JavaScript, fires your analytics, and then scrolls. Nothing is faked, so there is no gap to find. This is the case where “is this a real user?” has no clean answer, and it is where your bot mitigation has to earn its licence fee.
The short version
- Every request comes from a real rendering engine. Not a synthetic GET wearing a browser User-Agent string. Each counted request is one sub-resource a Chromium tab fetched because your markup asked for it.
- One page load is roughly sixty requests. Your capacity plan was probably built per request. Your CDN absorbs the cheap part of that, and the document and the XHR calls arrive at your origin.
- JavaScript challenges do not separate this from genuine traffic. A non-interactive challenge is passed by a browser processing the injected JavaScript, with no human involved. A real browser passes because it is one.
- It is one of only two simulations that reach your database. Real page loads run your template rendering, session lookups and the endpoints behind your logged-in views.
This is one of a series covering each of the ten simulation types Obsidio runs. They fail in genuinely different places, which is why a programme needs coverage across the categories rather than volume in one. For how they fit together, 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 Browser Flood actually does
The simulation loads the target URL over and over with full browser semantics. Not only the main document, but the entire sub-resource graph: HTML, CSS, JavaScript, images, and every XHR or fetch call the page makes once its scripts run. On workers that expose a WebView runtime, Obsidio drives a real Chromium tab through puppeteer-core.connect() and lets the page issue every request a browser naturally would.
That is the whole difference from an HTTP Flood, where a request is something the tool constructed and sent. Here a request is something the browser decided to make. After each load the worker takes one weighted in-page action: scroll half the time, click three times in ten, otherwise wait. The session in your analytics has a page view, a dwell time and an interaction, because all three happened.
Think of a room full of ordinary-looking people opening your homepage again and again, scrolling, clicking links, running your scripts, rather than bots hammering one endpoint.
Which of your controls should have caught this
It is worth walking the whole chain, because the answer here is rarely the control people expect.
Zone 1
Should any of these have stopped it?
- DDoS protection and scrubbing. There is volume to act on this time. Acting means deciding that a lot of real browser traffic is unwelcome.
- Firewall and WAF. Well-formed GETs for URLs that exist. Nothing to match on.
- Rate limiting. Per-IP counting has little to grip on traffic spread across thousands of devices, and any request budget must allow for a page load being tens of requests.
- Bot detection and challenges. This is the control that decides the run, and nobody can predict it for you. Real browsers execute JavaScript challenges honestly, so what is left is fingerprinting and behavioural scoring.
- CDN cache. Serves the static sub-resources from the edge. Cloudflare does not cache HTML or JSON by default, so the document and the XHR calls go through.
Zone 2
What takes the hit?
- Load balancer or reverse proxy. Handles a real concurrency increase, in the shape it was built for.
- Connection layer. Not the target. Browsers open and close connections the way browsers do.
- Web server. Serves every document request the cache handed on.
- Application and database. This is where it bites, and one of only two simulations that get this far. Rendering, sessions and logged-in queries all run for real.
- Auto-scaling. It will scale, because this looks like success. Whether that saves you or just bills you depends on what runs out first.
Zone 3
Would you find out?
- Dashboards. For once the graphs show it. Requests, page views and sessions all move, and your analytics counts the visits as visits.
- A check from outside your network. Tells you whether real users can still get a page, which server-side latency will not.
- Logging and your SIEM. No shortage of log lines, roughly sixty per page load. Finding the pattern in them is the work.
- Alerting. Thresholds fire. The alert says traffic is up, which is also what a good day looks like.
- On-call. Somebody gets paged, and the first hypothesis will be a marketing campaign. How long until the second one?
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.
With a slow attack, that third zone fails silently. Here everything fires, and the question is whether anyone reads it correctly inside the first hour.
Two execution paths, and why they are not interchangeable
Browser Flood runs one of two ways, and knowing which one produced your report matters more than any parameter on the form.
- The real browser path. With the WebView runtime available, exactly one Chromium tab per worker, driven over the DevTools protocol. Every reported request is one sub-resource the browser fetched. The parallel-loop and keep-alive settings are inert here, because the browser manages its own connections.
- The HTTP fallback. Without that runtime the simulation drops to a parallel pool of plain GETs against the main URL only, twenty loops per worker by default, connection reuse on. No sub-resource fetching, no JavaScript, no in-page actions. A sensible degradation, and a different test: it cannot tell you how your bot mitigation scores honest browser traffic.
Every worker holds a live Chromium instance, and browsers are expensive in memory, so Browser Flood buys fidelity rather than raw volume. Against modern edge defenses that is the better trade.
The distinction from its siblings is clean. HTTP Flood sends the configured method, URL and body as-is, aimed at your request-processing path. GoldenEye reaches origin by force, appending randomized cache-buster parameters so the edge cache cannot answer. Browser Flood needs neither trick: it asks for the real resource graph, the cache answers what it can, and the rest arrives at origin, as it would for a genuine visitor.
How to defend against a real-browser layer 7 DDoS attack
Be sceptical of anyone offering one answer here, including us. Telling this traffic apart from real users is the whole difficulty, so every control below is partial. What follows is what the vendors document, including the limits they admit to.
- Bot management, understood for what it is. Cloudflare’s bot score comes mostly from machine learning over headers, session characteristics and browser signals, plus heuristics matching known fingerprints. It is your strongest control here, and it produces a probability rather than a verdict. Cloudflare is candid about the edges: a new client’s first request generally carries no JavaScript Detections data, and JavaScript Detections does not guarantee a specific bot score.
- Challenges, understood for what they are. A managed challenge picks its type from the signals the browser presents. A non-interactive challenge is resolved by the browser processing injected JavaScript, usually in under five seconds, with no human action. So it filters clients that cannot run JavaScript. It does not separate a browser under software control from one under a person. Interactive challenges do need a human, and Cloudflare advises against defaulting to them.
- Know what your score threshold does. reCAPTCHA v3 returns a score for each request without user friction, and Google’s guidance is to act behind the scenes rather than block traffic. Sound advice, and it moves the decision to you: somebody has to have written down what each band does on each route.
- Rate limit on something better than an IP. Cloudflare rate limiting rules can count on a cookie value, a header value, or a JA3 or JA4 TLS fingerprint instead of the source address. That is stronger, with the caveat that a real Chromium presents a real Chromium fingerprint.
- Cache so your origin answers only what only it can answer. Cloudflare’s default cached types cover CSS, JS and the common image formats, and exclude HTML and JSON. Most of a Browser Flood therefore never reaches you, but the document rate and the API rate do. If your HTML is safe to cache at the edge, caching it changes this attack materially. If not, size your origin for the document rate and know that number in advance.
One thing we will not claim: no vendor documentation we found says any single control reliably separates a real browser driven by software from one driven by a person. If a vendor says theirs does, that is a claim to test rather than a claim to file.
What you can check yourself, and what it will get wrong
Read the configuration. Which bot management mode is active, what each score band does, whether challenges cover the paths that matter, and what your cache rules do with HTML. That tells you what the settings say, not how they score real browser traffic.
Rehearse the triage, not the alert. The problem here is interpretation, not silence. Show your on-call a graph of traffic up fortyfold with normal error rates and ask what they would do. It costs an hour and no traffic at all.
Run a load test against staging, with change-management sign-off, on infrastructure you own. Legitimate and useful, and of every attack type in this series, this is where a cheap check misleads you most.
A synthetic load generator sends the requests you tell it to send: a list of URLs, at a rate you choose, with a User-Agent string you type in. It does not parse your HTML to work out what the page needs. It does not run your JavaScript, so it never makes the XHR calls your dashboard depends on, and it produces none of the browser signals every detection engine above is built around.
So the result is wrong in two directions at once. On detection it reads pessimistic: your synthetic client is exactly what bot management exists to catch, it gets caught, and you conclude you are covered against something you never sent. On capacity it reads optimistic: you sized your origin against requests that never touched your rendering path, while the real thing is sixty requests where you tested one, and the expensive sixtieth is a database query.
Two errors pointing opposite ways is what makes this the worst false pass in the series. The test goes green, both halves of the reasoning are wrong, and nothing in the output says so.
How Obsidio tests a Browser Flood
Authorization comes before traffic. Domain ownership is verified by DNS TXT record before a single request is sent, runs ramp up gradually rather than starting at full load, and any run can be aborted live.
Load comes from more than 100,000 globally distributed real devices, ethically sourced real hardware in real consumer networks. Realism is not a way around your controls, it is the only way to learn what they do when traffic is genuine. A defense validated only against obviously synthetic traffic is not validated.
Three things worth watching during the window:
- Separate your document rate from your total request rate. Cached sub-resources dominate the total. The number that predicts failure is uncached document requests per second at origin.
- Watch the database, not the web tier. Connection pool saturation and query latency are usually where the ceiling sits. Keep the cache hit ratio and the scaling bill next to them.
- Record what bot management decided. Not only whether requests were blocked, but the score distribution and the action taken.
Be clear about what a report can prove. Obsidio measures how your edge responded and how connection counts behaved, because that is visible from outside your perimeter. Whether your SIEM correlated anything, whether an alert reached the right rotation, and whether the person who got it drew the right conclusion are answers only your own consoles hold. Have someone watching them while the run is live, because that half cannot be reconstructed later.
Strength is proven, not promised. A Browser Flood does not ask whether you can absorb traffic. It asks whether you can tell the difference, and whether anyone would notice if you could not.
Where this fits in a testing programme
Browser Flood sits in the application-layer flood category with HTTP Flood, GoldenEye and Apache Killer, at the realistic end of it. Its closest relative is elsewhere: Puppeteer Script, the other simulation that reaches your database, runs a script you write against a real browser to exercise one flow such as a login. Browser Flood asks what many real browsers loading your page do to you. Puppeteer Script asks the same about one specific flow.
Neither replaces the rest. An HTTP Flood concentrates load on your request-processing path without the browser overhead. Slowloris and RUDY never reach your application at all. Coverage across the ten simulation types is the point, not volume in one of them.
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 is an artifact you can file rather than a screenshot you have to vouch for.
To scope an authorized Browser Flood simulation against your own infrastructure, see the Obsidio platform or get in touch. Independent, neutral, verifiable: resilience you can prove.
