Back to Blog
DDoSCDNCachingTesting

CDN Cache and DDoS: Why the Edge Only Absorbs the Traffic It Caches

BlackNeuron Research Team
July 14, 2026
9 min read
CDN Cache and DDoS: Why the Edge Only Absorbs the Traffic It Caches

A CDN is sold as DDoS protection. In practice, it only protects the traffic it can serve from its own cache.

Every response the edge cannot cache is a response your origin has to generate itself. So under a flood of those requests, the CDN forwards all of them, and your origin takes the full load as if the edge were not there.

The dashboard stays green the whole time. Caching almost nothing looks identical to caching everything, right up until the origin falls over.

This is edge cache absorption, and it is one of the quietest gaps in cloud resilience. It is also exactly the kind of assumption a thorough DDoS test is meant to check rather than trust.

The framing here is defensive throughout. These are the measurements you take against your own site, under authorization, so you know what your edge actually absorbs before an attacker finds out for you.

Why a CDN only absorbs what it caches

The mistake is treating a CDN as a wall. It is not a wall. It is a cache with a filter in front.

When a request arrives at the edge and the object is in cache, the edge answers it directly. Your origin never sees that request. That is absorption, and it is the entire mechanism by which a CDN blunts a flood.

When the object is not cacheable, the edge does the honest thing: it forwards the request to your origin and passes the answer back. Your origin did the work. Repeat that a few hundred thousand times a second and the edge has faithfully relayed a flood straight to your server.

So the protection a CDN gives you against volumetric load is not a property of the vendor. It is a property of your response headers, which decide what the edge is allowed to keep.

The arithmetic is unforgiving. The chart below is a simple simulation of one workload under a fixed flood, swept across cache hit-ratios.

2026-07-14T18:47:11.610092 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 10000 20000 30000 40000 50000 Offered flood rate (rps) 0 10000 20000 30000 40000 50000 Requests reaching origin (rps) origin capacity Simulated: requests reaching the origin during a rising flood, with edge caching on versus off A CDN only absorbs the flood it caches load the control sheds caching off (all hits origin) edge caching on BlackNeuron
Simulated: requests reaching the origin during a fixed-rate flood, by CDN edge cache hit-ratio. At a 0 percent hit-ratio the origin absorbs the entire flood; at 95 percent it sees a twentieth of it. Below the origin-capacity line the server survives, above it the server is overwhelmed.

At a 0 percent hit-ratio the origin sees the whole flood. At 95 percent it sees a twentieth of it. Installing a CDN does not move you along that curve. Caching does.

The broader picture of what a flood actually targets is in Understanding DDoS Attack Vectors.

What quietly makes your responses uncacheable

None of the reasons below require a misconfigured CDN. They are ordinary application defaults, and any one of them on the wrong path turns your expensive edge into a fast reverse proxy.

What quietly forces a response past the edge Correct-looking defaults that stop the edge from keeping a copy. Any one on the wrong path is origin load. Cache-Control: no-store / private Cache-Control: max-age=0 / no-cache Set-Cookie on the response cf-cache-status: DYNAMIC (no cache rule) Genuinely dynamic HTML expected, and correct Request reaches your origin the origin does the work under a flood, that is unfiltered load Dynamic HTML reaching origin is fine. The problem is your static assets landing here. BlackNeuron
What blocks edge caching: a no-store or private directive, a max-age of zero, a Set-Cookie on the response, and a provider default such as Cloudflare DYNAMIC each force the request through to the origin instead of letting the edge answer it.

no-store and private

Frameworks apply these by default to anything that looks dynamic. Cache-Control: private or no-store is perfectly correct for a logged-in account page. It also tells every shared cache, including your CDN, not to keep a copy. Every hit then goes to origin.

max-age=0 and no-cache

These do not forbid caching outright, but they force the edge to revalidate with the origin on every request. The origin is back in the loop for each hit, which is the exact thing you were trying to avoid under load.

A Set-Cookie on the response

A response that sets a cookie is treated as user-specific and is not stored by shared caches. One analytics or CSRF cookie attached to your HTML, or worse to your static assets, can make a large part of your site uncacheable without anyone noticing.

The provider default

On Cloudflare, anything without an explicit cache rule returns cf-cache-status: DYNAMIC, which means the edge did not cache it. Most sites never add the rule, then assume the orange cloud is caching for them. It is not.

Genuinely dynamic pages, which is normal

Some responses simply cannot be cached, and that is fine. A personalized dashboard should be private. The point is not to cache everything. It is to know which responses hit your origin, and to make sure the ones that carry the flood are not among them.

Static assets are what a flood actually hits

A volumetric flood is overwhelmingly repeated requests for the same objects: the scripts, stylesheets, fonts, and images that every page load pulls. These are precisely the things that should live in the edge cache.

When they do, the edge absorbs the repetition and the origin barely notices. When they do not, every repeat is a fresh trip to your server.

Where a flood actually lands: what the edge keeps, and what it forwards The edge answers cacheable objects itself. Everything it cannot cache, it relays straight to your origin. Incoming flood repeated requests CDN edge cache + filter HIT? or forward? cacheable → HIT Served from the edge cache the origin never sees it · absorbed JS · CSS · fonts · images → the objects a flood repeats uncacheable / MISS → forwarded Origin server generates every forwarded request no-store · private · Set-Cookie · DYNAMIC → the reasons it cannot keep it Your origin load under a flood is exactly the share the edge could not keep. Raise what it keeps, and the flood mostly dies at the edge. BlackNeuron
Cache absorption flow: cacheable static assets are answered from the edge and never reach the origin, while uncacheable or cookie-tagged responses are forwarded straight through, so the origin load is decided by what the edge is allowed to keep.

The split is the whole story. Cacheable assets are absorbed at the edge. Uncacheable responses pass through. Your origin load under attack is set by which side of that line your busiest objects fall on.

This is the same reasoning behind hiding your origin so the flood cannot skip the edge entirely, covered in origin IP exposure. Absorption and exposure are two halves of one question: does the edge see the traffic, and does it keep it.

How to test your edge absorption

You cannot tell from a dashboard. You have to ask the edge directly, for your own URLs, and read what it says.

The mechanism is a two-step check per URL. Fetch it once to warm the edge, fetch it again, and read the cache headers on the second response.

# The second fetch is the one that tells the truth.
curl -sI https://example.com/assets/app.js | grep -iE 'cf-cache-status|x-cache|age|cache-control'

A cf-cache-status: HIT, an x-cache: Hit, or an Age greater than zero means the edge served it and your origin was untouched. A MISS on a warmed object, or a DYNAMIC, or a no-store, means that request reached your server.

Do that across your landing page, your static assets, and a few internal pages, and you have an honest absorption number. Repeating that by hand across a whole site is tedious, which is the usual reason it never gets done.

Or run it automatically

We packaged this exact check into a small, open-source Rust tool, edge-cache-check. It is free on GitHub: github.com/BlackNeuron-ai/edge-cache-check.

Point it at a domain you own. It fingerprints your edge, samples your landing page and its static assets, probes each URL twice, and reports what fraction the edge actually serves from cache, with the reason anything falls through.

cargo run --release -- example.com

It prints EDGE for anything served from cache, ORIGIN for anything that reaches your server, and a STRONG, PARTIAL, or WEAK verdict for the whole sample. No attack traffic, authorized self-testing only.

The full build story, and why a two-fetch design matters, is on dev.to: Your CDN says it is protecting you. I checked, and 0% of the traffic was cached. The code is on GitHub at github.com/BlackNeuron-ai/edge-cache-check.

Keeping the whole exercise inside a safe boundary is the subject of running a DDoS test without disrupting production.

Fixing weak absorption

Finding a low absorption number is only useful if the fix is confirmed by a re-test. The remediations, in order.

Make your static assets genuinely cacheable

Serve assets with a long max-age and immutable, and make sure nothing attaches a Set-Cookie to them. Fingerprinted asset filenames make aggressive caching safe, because a change ships a new URL rather than mutating an old one.

Add an explicit cache rule at the edge

Do not trust the provider default, which on Cloudflare is to cache nothing. Put a cache rule on the paths that can tolerate it, so the edge keeps what it is allowed to keep. The provider-specific contours are worked through in Cloudflare DDoS testing.

Defend the paths that cannot be cached

For genuinely dynamic routes, absorption is not the tool. Edge rate-limiting and a WAF are, so the origin is not the only thing standing between a flood and your application. Turning all of this into a single posture score is covered in DDoS resilience testing.

Absorption is one pillar of resilience, not the whole of it, but it is the pillar people assume they get for free the moment they put a CDN in front. Our Patent-Pending adaptive methodology exists precisely because these gaps hide until something changes its behavior in front of them.

FAQ

Does a CDN stop DDoS attacks?

Only for the traffic it caches. A CDN absorbs a flood by answering repeated requests from its edge cache. Any response it cannot cache is forwarded to your origin, so a flood of uncacheable requests reaches your server unfiltered.

What is a good CDN cache hit-ratio?

For static assets, aim high, well above 90 percent, because those objects carry the repetitive load a flood generates. Dynamic, personalized pages will and should have a low hit-ratio; the goal is a high ratio on the objects that dominate volume, not on everything.

Why is my CDN not caching my site?

The usual causes are a Cache-Control: private, no-store, or max-age=0 header, a Set-Cookie on the response, or a provider default such as Cloudflare DYNAMIC where no cache rule was ever added. Each forces the request through to the origin.

How do I measure what my edge actually absorbs?

Fetch each URL twice and read the cache headers (cf-cache-status, x-cache, Age) on the warmed second response. A hit means the edge served it; a miss or a no-store means your origin did. The open-source edge-cache-check tool automates this across a whole site.

The dashboard is not the test

Weak edge absorption survives in well-run environments because everything looks correct from the outside.

The site loads through the CDN. The provider dashboard is green. The edge is clearly doing something.

None of that reveals the one number that decides your fate under a flood: how much of your traffic the origin still has to serve itself.

The only way to know is to ask the edge, object by object, and count what it keeps.