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

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.

Application-layer floods

Slow attacks

Connection exhaustion

Protocol-specific and scripted

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.

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.

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:

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.

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

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.

Application-layer floods

Slow attacks

Connection exhaustion

Protocol-specific and scripted

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_key to $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:

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.

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:

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.

An HTTP flood attack contains nothing malformed. Every request is complete, valid, correctly framed HTTP, the kind your server was built to answer. Taken one at a time, they are indistinguishable from a customer checking a balance. Taken together, they are more work than your stack can do.

So this is the one attack class where your front-line defenses finally have a signal to act on, which shifts the question. It is no longer whether DDoS protection can see the traffic. It is whether the threshold was set to the right number, whether the endpoint being hit is cacheable, and whether the smallest limit in your chain is the one you think it is. Configuration answers, all of them, and configuration goes stale quietly.

The short version

This is one in a series covering each of the ten simulation types Obsidio runs, one post per attack. They fail in genuinely different places, which is the reason a test programme needs coverage across the categories rather than volume in one of them. 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.

Application-layer floods

Slow attacks

Connection exhaustion

Protocol-specific and scripted

What an HTTP flood attack actually does

Obsidio’s HTTP flood runs several independent request loops on each worker. A loop fires one request, waits for the response, and fires the next the moment it arrives. No pause, no think time, no randomization: the configured method, URL and body go out exactly as given, every time.

The pressure comes from concurrency, not bandwidth. Twenty loops per worker across a run’s workers keeps a large number of requests in flight at the same moment, and each one occupies something on your side: a slot in the request queue, an application thread, a pooled connection, a query the database has to plan and run. A volumetric attack fills your pipe. This one fills your capacity to do work.

Picture twenty people standing at a shop counter at once, each asking for the same item again the instant they are served. The shop never gets a break.

Sending identical requests has one consequence worth naming: a cache in front of you can serve them. That is the useful part. If a plain repeated request never reaches origin, you have learned something real about your edge. If it reaches origin every time, you have learned something you can act on.

Which of your controls should have caught this

It is worth walking the whole chain a request passes through, because the answer is rarely the control people expect. Three groups matter: what sits in front, what absorbs whatever gets through, and whether anything tells you it happened.

Zone 1

Should any of these have stopped it?

  • DDoS protection and scrubbing. It has a real signal here, so it should act. Whether it does depends on thresholds set against your normal traffic.
  • Firewall and WAF. Sees complete, valid requests with nothing to match on. It contributes only where it also enforces a rate.
  • Rate limiting. Most likely to decide the outcome. What it keys on, the rate it allows and the burst it forgives are the whole answer.
  • Bot detection and challenges. Static agent string, no browser behaviour, so a challenge ought to catch it. Whether the hit endpoint sits behind one is the open part.
  • CDN cache. A cacheable path absorbs almost all of this. A login form or an API call reaches origin every time.

Zone 2

What takes the hit?

  • Load balancer or reverse proxy. Forwards valid requests, because that is its job. Its upstream pool becomes an early ceiling.
  • Connection layer. Holds up well. Sockets open and retire normally, and with reuse on, a few carry many requests each.
  • Web server. Worker and thread pools saturate, and once they do, new requests queue instead of failing.
  • Application and database. This is where it bites. Requests cost CPU, and every query takes a connection from a pool far smaller than the request rate.
  • Auto-scaling. It sees the spike and reacts, the opposite of the slow-attack case. Whether it reacts fast enough, and whether more app servers on one database pool help, is what a test shows.

Zone 3

Would you find out?

  • Dashboards. Requests per second climbs visibly. This is the attack your graphs are good at.
  • Logging and your SIEM. The inverse failure. Log lines multiply with request volume, so ingestion caps get hit, licence overage bites, events get sampled away, and the window you most need is the one that got truncated.
  • Alerting. Something fires. Whether it is one actionable alert or several hundred correlated ones is a tuning question only load answers.
  • On-call. Somebody gets paged, probably repeatedly. Whether the page says enough to act on is the part worth testing.
  • A check from outside your network. Reliable here. It settles whether users are affected while internal metrics are still being argued about.

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.

That third group is the part teams discover late. A control that works but is invisible is still a finding, and so is a control that reports more than anyone can read.

The parameters, and the connection reuse tradeoff

Five settings shape the run, and each moves the load somewhere different:

That last one deserves its own paragraph. With reuse on, a socket goes back to the pool after each response, so the next request skips the TCP and TLS handshake and the load lands almost entirely on request processing. Turn it off and every request opens a fresh socket: achievable rate drops and part of the load moves onto connection setup, which tests something else.

Your server, not the client, decides how long a pooled socket survives. nginx defaults to keepalive_timeout 75s and keepalive_requests 1000, so a connection closes after a thousand requests or 75 idle seconds whatever the client would prefer. Pairing reuse with a server that answers Connection: close builds a pool torn down after every response, which is overhead on both sides.

How this differs from GoldenEye and Browser Flood

All three sit in the application-layer flood category and they are not interchangeable. GoldenEye appends randomized query-string parameters and rotates the user agent, so it reaches origin even behind a CDN and blunts header-based filtering. HTTP flood does the opposite on purpose: identical requests, so your cache is allowed to do its job and you get to measure whether it does. Browser Flood drives a real Chromium tab on each worker and fetches the whole sub-resource graph a browser requests.

Run HTTP flood for raw request-processing capacity, GoldenEye to bypass the cache deliberately, Browser Flood for realistic user load.

How to defend against an HTTP flood attack

Defending against this is mostly arithmetic. Four things carry most of the weight:

Every value above lives in a file somebody edited once, which is why reading the config is weaker evidence here than running the attack.

What you can check yourself, and what it will get wrong

Three of these questions you can answer today without help. It is worth knowing which, and where a cheap check misleads you.

Read the configuration and do the arithmetic. Write down the rate limit and its key, which paths are cached, the worker ceiling, the process pool, the database connection limit. The smallest number is your capacity. That tells you what the numbers are. It does not tell you the chain behaves the way the arithmetic suggests, because queuing, retries and timeouts interact in ways no config file shows.

Test alert routing on its own. Fire a synthetic alert and see whether it reaches the right rotation and whether anyone acts. That verifies the plumbing, and it needs no attack traffic. It says nothing about how the same plumbing behaves when the volume is real.

Run a single-source load test against a staging environment, with change-management sign-off, on infrastructure you own. That is legitimate, and for capacity curves it is genuinely useful. It is also where the trap is.

One machine does not merely fail to answer the questions in the first zone. It answers several of them wrongly, in a reassuring direction. A per-source rate limit catches a single origin immediately, bot detection flags one client behaving oddly, reputation filtering handles it, and the edge gets declared sound.

A real distributed flood does not present that way. Thousands of residential addresses each send a modest, plausible number of requests, no single source crosses any per-source threshold, and the aggregate lands on your origin regardless. The controls you most need to trust are the ones a single-source test grades too generously.

Detection has the same asymmetry. A small test produces a trickle of logs, so it cannot say whether your ingestion cap gets reached, whether events start getting dropped, or whether the on-call engineer can find the useful alert among the rest.

How Obsidio tests an HTTP flood

Obsidio runs this 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 set the loops per worker, the method, the payload and whether connections are reused, then watch your stack. Load comes from 100,000+ globally distributed real devices, so per-source limits and reputation filtering get tested the way a distributed attack tests them.

Three things to watch during the run:

Every request flows through a response analyzer, so each bucket report carries what the edge did: WAF responses, scrubbing behaviour, rate-limit rejections, captcha challenges. Obsidio measures that, because it is visible from outside. Whether your SIEM ingested the event 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. An HTTP flood does not look for a flaw in your code. It asks what the smallest number in your stack is, and whether anybody has checked it lately.

Where this fits in a testing programme

An HTTP flood is close to the exact inverse of Slowloris. The slow attack sends almost nothing, walks past volume-based defenses, stops at the web server’s connection pool, and produces silence where you would look for it. The flood is loud, gives your edge controls something to act on, reaches the database, and buries the finding in telemetry. Hardening for one teaches you little about the other. A programme built on floods alone leaves the slow class unmeasured, and one built on slow attacks alone never finds its capacity ceiling.

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 result of a run is an artifact you can file rather than a screenshot you have to vouch for.

To scope an authorized HTTP flood simulation against your own infrastructure, see the Obsidio platform or get in touch with the team. Independent, neutral, verifiable: resilience you can prove.

A Slowloris attack can take a web server offline from a single laptop on a home connection. It sends almost no data, generates almost no requests per second, and looks nothing like the traffic spike your DDoS protection was bought to stop.

That is the problem. Most defenses are tuned for volume: gigabits per second, requests per second, packets per second. Slowloris wins by sending as little as possible, very slowly, for as long as possible. If your protection keys on rate, this attack walks straight past it.

The short version

This is the first in a series covering each of the ten simulation types Obsidio runs, one per attack. They fail in genuinely different places, which is the reason a test programme needs coverage across the categories rather than volume in one of them. 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.

Application-layer floods

Slow attacks

  • Slowlorisyou are here
  • RUDY

Connection exhaustion

Protocol-specific and scripted

What a Slowloris attack actually does

Slowloris opens many parallel TCP connections to a web server and then refuses to finish any of them. Each connection sends the beginning of an HTTP request (the request line, a Host header) and then trickles one more header line every few seconds. The terminating blank line that tells the server “the request is complete, you may now process it” never arrives.

The server does the reasonable thing: it waits. It holds the connection open, keeps a worker or a slot in the connection pool assigned to it, and waits for the rest of a request that will never come. Multiply that by a few hundred connections and the pool is full. Legitimate users then get a timeout, not an error page, because the server never gets far enough to serve them.

Think of twenty people phoning a shop, each starting to place an order and then stretching every sentence out so the call never ends. The staff are polite, so they stay on the line. Nobody else can get through.

The technique is not new. The original Slowloris tool was released on 17 June 2009, and it still works, because the thing it exploits is not a bug. It is the assumption, built into how HTTP servers handle connections, that a client which has started talking intends to finish.

Which of your controls should have caught this

It is worth walking the whole chain a request passes through, because the answer is rarely the control people expect. Three groups matter: what sits in front, what absorbs whatever gets through, and whether anything tells you it happened.

Zone 1

Should any of these have stopped it?

  • DDoS protection and scrubbing. Waits for a traffic spike. There is not one.
  • Firewall and WAF. Inspects complete requests, and this one never completes. It helps only if it buffers the full request before passing it upstream, and plenty of deployments do not.
  • Rate limiting. Counts requests. This sends far too few to cross a threshold.
  • Bot detection and challenges. Never loads a page, so there is nothing to challenge.
  • CDN cache. Nothing cacheable is in play, so it makes no difference either way.

Zone 2

What takes the hit?

  • Load balancer or reverse proxy. Absorbs the whole attack if it buffers requests. Worth confirming that yours does.
  • Connection layer. Fills with held-open sockets.
  • Web server. This is where it bites. Worker slots run out unless your timeouts and a minimum transfer rate cut the connections off first.
  • Application and database. Never reached. The attack stops earlier.
  • Auto-scaling. Sees no load spike, so it never scales.

Zone 3

Would you find out?

  • Dashboards. Requests per second look completely normal.
  • A check from outside your network. The one thing that reliably catches this, if you run one.
  • Logging and your SIEM. Unfinished requests often produce no access-log entry, so there may be nothing to correlate afterwards.
  • Alerting. Nothing crosses a threshold.
  • On-call. Nobody is paged. The service is down and the room is quiet.

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.

That last group is the part teams tend to discover late. A control that works but is invisible is still a finding, and so is a control that fails without anyone noticing.

Four vectors, four different failure modes

“Slowloris” is often treated as one attack. In practice, holding a connection open has several distinct forms, and a server or proxy hardened against one can still be exposed to another. Obsidio’s Slowloris simulation runs four:

The distinction matters when you configure a fix. A header-read timeout stops slow header and does nothing for slow read. A body timeout stops slow POST and does nothing for either. Testing one vector and declaring the class handled is how teams end up surprised.

Which servers are exposed

Susceptibility comes down to architecture. Servers that dedicate a thread or process per connection run out of them; event-driven servers multiplex connections and degrade far more gracefully.

Apache 1.x and 2.x are the canonical vulnerable case in the original disclosure, along with dhttpd and the Flask development server. Servers documented as resilient include nginx, lighttpd, Hiawatha, Cherokee and IIS. But “resilient architecture” is not the same as “safe deployment”: nginx still ships default timeouts of client_header_timeout 60s and client_body_timeout 60s, which is 60 seconds of free occupancy per connection, and an application server behind the proxy may have no such protection at all.

Check what actually terminates the connection in your stack, not what the marketing page for one component says.

How to defend against a Slowloris attack

Mitigation is about refusing to wait indefinitely, and about limiting how much one client can hold. Four controls do most of the work:

Each of these is a configuration value in a file somewhere, which means each of them can be wrong, stale, or overridden by a later change nobody re-checked.

What you can check yourself, and what it will get wrong

Three of those questions you can answer today without help. It is worth knowing which, and worth knowing where a cheap check misleads you.

Read the configuration. Confirm the timeout module is loaded, look at the actual values, check whether your proxy buffers. This tells you the setting exists. It does not tell you the setting holds under concurrency, which is a different claim.

Test alert routing on its own. Fire a synthetic alert and see whether it reaches the right rotation and whether anyone acts. That genuinely verifies the plumbing, and it needs no attack traffic at all.

Run a single-source test against a staging environment, with change-management sign-off, on infrastructure you own. This is legitimate and useful. It is also where the trap is.

A test from one machine does not merely fail to answer the earlier questions. It answers several of them wrongly, in a reassuring direction. Your per-IP connection limit catches one source immediately. Bot detection flags a single odd client. Reputation filtering handles it. Every control in the first group appears to work, and you conclude you are covered.

Then a real distributed attack arrives from thousands of residential addresses and those same controls behave completely differently, because they key on source concentration and traffic realism, which is exactly what one machine cannot reproduce. The cheap check produces a false pass on the controls you most need to trust.

The same asymmetry applies to detection. A synthetic alert proves the routing works. It cannot tell you whether your dashboards show anything during an actual slow attack, or whether your logging captures it, because those failures depend on the traffic being real.

How to test whether your Slowloris defense works

You cannot infer this from a configuration review. Whether the pool actually holds depends on the interaction between your proxy, your web server, your application server and your connection limits under real concurrency, and that interaction is not visible in any single config file.

Obsidio runs Slowloris 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 vector, the number of sockets held per worker, and the trickle interval, then watch what your stack does. Because load comes from 100,000+ globally distributed real devices rather than a handful of datacenter instances, a per-IP connection cap gets tested the way an actual distributed attack would test it.

Two things to watch during the run, both of which trip people up:

Plan for the detection questions too. Obsidio can report how your edge controls responded and how many connections were held, because that is visible from the outside. Whether your SIEM recorded 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. Slowloris does not test your bandwidth. It tests whether anyone has checked your connection timeouts since the day they were set.

Where this fits in a testing programme

Slowloris belongs in the slow-attack category, alongside RUDY, which starves a request body rather than a header block. Both probe connection handling and timeout configuration. Neither is covered by an application-layer flood test, which is why a programme that only runs HTTP floods leaves this whole class unmeasured.

The ten simulation types fail in genuinely different places. An HTTP flood inverts almost everything above: the front defenses finally have something to act on, the damage travels as far as your database connection pool, and the detection problem flips from hearing nothing to drowning in alerts. Coverage across the categories is the point, not volume in any 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 result of a run is an artifact you can file rather than a screenshot you have to vouch for.

To scope an authorized Slowloris simulation against your own infrastructure, see the Obsidio platform or get in touch with the team. Independent, neutral, verifiable: resilience you can prove.

Most teams learn the limits of their DDoS protection during a real attack. That is the worst possible time to find out that a WAF rule was too loose, or that an origin server was never shielded at all. Knowing how to test your DDoS protection before an incident is the difference between a controlled finding and a public outage.

This guide is written for the people who own that risk at a regulated institution: security engineers, WAF and load-balancer operators, and the compliance officers who have to prove resilience to a regulator. It covers what a proper test measures, how to run one without knocking yourself offline, and how to turn the result into evidence an auditor will accept.

What testing your DDoS protection actually means

A DDoS test sends controlled, realistic attack traffic at infrastructure you own, then measures whether your defenses absorb it and how your service behaves while under load. The goal is not to break the site. It is to find the point where protection degrades, so you can fix it on your own schedule.

Two things it is not:

The value is in the specificity. A good test tells you which layer failed, at what volume, and whether your mitigation triggered in time.

Authorization comes before the first packet

The single rule that separates legitimate resilience testing from an attack is authorization. You test only assets you own or are explicitly contracted to test, and you prove that ownership before any traffic flows.

Obsidio enforces this with DNS TXT ownership verification. You place a record on the domain you intend to test, and no simulation can start until that record is confirmed. Runs are controlled, ramped up gradually, and can be aborted live. This is what makes the exercise defensible to a legal team and repeatable for a compliance file: the authorization is recorded, not assumed.

Treat this as the first control you are testing, not a formality. If your own change-management process cannot produce a signed-off, time-boxed authorization for a test, it will not produce one for an incident either.

The attack types worth testing against

DDoS is not one thing, and protection that stops one class can be blind to another. A test programme should exercise the distinct ways a service fails. Obsidio’s simulation catalog spans four categories, each stressing a different part of your stack:

The catalog grows as attacker techniques do. For a first test, aim for coverage across categories: at minimum one application-layer flood and one slow or connection-exhaustion attack, because they fail different parts of your stack.

A method you can repeat: Configure, Test, Adapt, Retest

A single test is a snapshot. Resilience is proven by the loop. Obsidio structures each run in four stages:

Then you adapt. Tune a WAF rule, adjust a rate limit, raise a timeout, and retest to prove the fix worked. The retest is the part teams skip and the part that matters most. A fix you did not verify under load is a hypothesis, not a control.

What realistic traffic requires

A test is only as honest as the traffic behind it. Defenses that key on obvious signatures, such as a single source subnet, a fixed user-agent, or datacenter IP ranges, will “pass” a test that uses those same tells, then fail against a real botnet that does not.

Obsidio generates load from 100,000+ globally distributed real devices rather than virtualized instances, sourced from ethically sound providers. Traffic arrives from real hardware and smartphones across many networks and regions, so you are testing your defense against something that looks like a genuine distributed attack rather than a lab artifact your WAF can trivially filter.

Turning a test into compliance evidence

For a regulated institution, a passing test that leaves no record is worth little. The regulatory frameworks now expect the evidence, not just the effort.

Under the EU’s Digital Operational Resilience Act (DORA), financial entities must run a resilience testing programme with, at minimum, appropriate tests of the ICT systems supporting critical or important functions each year (Article 24). Significant entities must also undergo advanced threat-led penetration testing at least every three years (Article 26). NIS2 obliges essential and important entities to adopt risk-management measures and test them, and FINMA expects Swiss institutions to manage operational resilience on a comparable basis.

What these frameworks share is a demand for demonstrable, repeatable testing, the kind of assurance you can hand to an auditor or a board. Obsidio produces cryptographically attested, tamper-evident reports mapped to FINMA, DORA, and NIS2, generated inside Trusted Execution Environments so the result cannot be quietly edited after the fact. The output of a test becomes the artifact in your compliance file.

Strength is proven, not promised. A test you can attest to is what turns “we are resilient” into something an auditor can check.

Where to start

If you have never tested your DDoS protection, begin with a single authorized, low-intensity application-layer run against a staging environment or a scheduled window. Watch how your monitoring and mitigation respond, then widen the scope from there. The first test rarely produces a clean pass, and that is exactly why you run it before an attacker does.

To scope a controlled simulation against your own infrastructure, get in touch with the Obsidio team or request a demo. Independent, neutral, verifiable: resilience you can prove.