Most explanations of a content delivery network stop at the sentence “it puts your files closer to your users”, which is true and almost useless. It does not tell you what gets stored, what does not, who decides, what happens when the stored copy is wrong, or why your dashboard still takes two seconds after you turned one on.
So: a CDN is a cache with a network attached. Both halves matter, and the cache half is the one that decides whether it helps you.
Why does distance cost anything?
Because information does not move instantly. In fibre, light travels at roughly 200,000 kilometers per second (124274 mile per second) which is about two thirds of its speed in a vacuum, slowed by the refractive index of glass. That gives a hard floor of about 0.01 milliseconds per kilometer of round trip, and real routes run around 1.6 times worse because cables follow coastlines, political borders and existing rights of way rather than great circles.
Now count the round trips. Opening a fresh HTTPS connection costs one for TCP, one for the TLS 1.3 handshake, and one for the request and its response — three before a single byte of your HTML is in the browser. A visitor in São Paulo hitting a server in Mumbai is 13,600 km away, which is roughly 220 ms per round trip, which is roughly 650 ms of waiting before your application has done anything at all.
What distance costs you, before a single byte of work
Origin only — one server in Mumbai
—
Nearest edge — cached copy
—
A visitor in Singapore waits about 190 ms for the first byte from Mumbai and about 2 ms from the nearest edge.
That number is not a function of your framework, your database or your hosting bill. It is geography. The only way to make it smaller is to move the answer closer to the question, and that is the entire premise of an edge network.
What actually happens when a request hits a CDN?
Four things, in order.
Anycast routing picks a location. A CDN uses Anycast routing to seamlessly connect visitors to the most optimal location. By broadcasting identical IP addresses from every global site via BGP, the network automatically routes packets to the “closest” data center based on network topology. From the browser’s perspective, this process is entirely invisible, it doesn’t know or care if the physical server is in Sydney or Mumbai because the IP address it connects to is exactly the same.
TLS terminates at the edge. The handshake happens in that nearby building rather than at your origin, which is why a CDN speeds up even completely non cacheable requests: you have moved three round trips from 13,600 km down to 50.
The cache key is computed. Most CDN nightmares are cause by the cache key computation - a step developers often ignore. The edge creates this key (typically combining the HTTP method, the Host, and the full URL) to figure out what content to serve. It’s a delicate balance. If you leave out a crucial variable, you’ll end up serving the wrong page to the wrong person. On the flip side, if you accidentally include unique identifiers like tracking parameters or session cookies, you’ll shatter your cache hit rate to zero. Both mistakes just look like a broken CDN.
Hit, miss, or stale. A hit is answered from local storage. A miss is fetched from your origin, stored, and then answered. A stale entry can, if you asked for it, be answered immediately while a fresh copy is fetched in the background.
The same page, three cache outcomes
Nothing in the edge cache. The edge opens a connection to the origin and waits for it.
A fresh copy is already at the edge. The origin is never contacted.
The copy expired, but stale-while-revalidate lets the edge serve it now and refresh it afterwards. The visitor never waits for the origin.
Look at the third row. stale-while-revalidate is the single highest-value directive
most sites are not using: it decouples “this copy has expired” from “this visitor must
wait”. The expiry becomes a background job instead of a queue.
Who decides what gets cached?
You do, in the response headers, and the rules are specified in RFC 9111 rather than invented by each vendor. The two directives that matter most are read by different caches:
max-ageis for private caches — the visitor’s browser.s-maxageis for shared caches — the CDN, and any proxy between you and it.
If you set only max-age, the shared cache falls back to it. That is the most common
cache misconfiguration in the wild, and it has two failure modes depending on which
number you picked: a short one gives you a CDN that barely caches anything, and a long
one gives you an edge serving yesterday’s homepage to everybody, with no way to fix it
except a purge.
Build a Cache-Control header and see who obeys it
HTML that changes when an editor publishes: keep the browser copy short, let the edge hold it for a day, and purge on publish.
Two further directives are worth knowing properly:
immutabletells the browser not to revalidate at all, not even with a conditional request. It is correct precisely when the URL contains a content hash —app.9f2c1b.jscannot change meaning under that name — and wrong everywhere else.Varyadds a request header to the cache key.Vary: Accept-Encodingis fine and expected.Vary: Cookieis a loaded gun: every distinct cookie value becomes its own cache entry, so the hit rate collapses to nothing while the storage bill does not.
What should you put behind it, and what should you not?
The clean split is by what a response depends on.
Cache hard: fingerprinted JavaScript and CSS bundles, images, fonts, video
segments, downloads, and anything else whose URL changes when the bytes change. A year
with immutable is not aggressive here, it is correct.
Cache briefly, purge on write: marketing pages, blog posts, documentation, product
listings, API responses that are the same for everyone. Short s-maxage, generous
stale-while-revalidate, and a purge call in whatever publishes the content. This is
where most of the unclaimed win sits.
Do not cache: anything derived from who is asking. Dashboards, carts, account
pages, authenticated APIs. You can still route these through the CDN — the TLS and
routing benefits are real — but mark them private, no-store and mean it. A
personalized page served from a shared cache is not a performance problem, it is a
data breach.
The awkward middle case is a page that is 90% identical for everyone and 10% personalized, which is most of the modern web. Three ways out, in ascending order of effort: cache the shell and fetch the personal part with a second request; use edge personalization, where a small piece of code at the edge stitches the two together; or split the page so the personal part is a separate cached fragment keyed on a group rather than a user — logged-in versus not, region, currency — which gets you a handful of cache entries instead of one per person.
Which provider, and does the choice matter?
Less than the marketing suggests, and more than nothing.
- Cloudflare — the largest free tier in the business, DNS, DDoS protection and the Workers runtime in the same product. The default answer for most projects, and the one whose free plan genuinely covers a small site.
- Fastly — instant purge measured in tens of milliseconds and VCL for configuration, which makes it the pick when caching logic is genuinely complicated and you want it version-controlled.
- Amazon CloudFront — the sensible choice when your origin is already S3, ALB or Lambda, because the integration and the private-origin story are already built.
- Akamai — the largest footprint and the deepest enterprise contracts; you will usually meet it through an existing relationship rather than by choosing it.
- Bunny and similar independents — dramatically cheaper per terabyte, fewer features around the edge. Excellent for straightforward asset and video delivery.
- Google Cloud CDN and Azure Front Door — the same logic as CloudFront, for the other two clouds.
The differences that will actually affect you, in rough order: how fast a purge propagates, whether egress to the CDN from your origin is free or metered, whether tiered caching is on by default, and what the edge compute story looks like if you think you will need one.
That last point is worth a sentence of its own. Every major CDN now runs your code at the edge — Cloudflare Workers, Fastly Compute, Lambda@Edge, Akamai EdgeWorkers. That turns the CDN from a cache into a place you can deploy to, which is a genuinely different thing, and it is why “which CDN” is starting to be an architectural decision rather than a procurement one.
Do you actually need one?
Sometimes, no. It is worth saying plainly, because almost nothing else written about CDNs will.
Do you actually need one?
Reading
Tick what is true of your project.
Nothing ticked yet, so there is nothing to weigh.
Three cases where the honest answer is no, or not yet:
Your users are all in one place, and so is your origin. A regional business with a server in the same region already has 20 ms round trips. Three of them is 60 ms. The edge cannot meaningfully improve on that, and you have added a vendor, a cache to invalidate and a second place to look when something breaks.
Every response is personalized. An internal admin tool where every byte depends on the session gets the TLS-termination benefit and nothing else. Sometimes that alone is worth it. Often it is not worth the configuration.
You have not fixed the origin yet. A CDN in front of a slow application makes the cached paths fast and leaves every uncached path exactly as slow as it was, while making it harder to see which is which. Profile first. Add the cache to a system you understand.
And one case where the answer is yes regardless of geography: you are being attacked, or you expect to be. Absorbing a volumetric attack requires more capacity than the attacker can generate, and buying that capacity yourself is not a thing you can do at a sensible price. This is a large part of why sites with entirely local audiences still sit behind Cloudflare.
What breaks, in practice
Four failure modes account for most CDN incidents we have been called into.
The cache key includes something it should not. Marketing appends ?utm_source= to
every link, each variant becomes its own entry, and the hit rate falls off a cliff on
the exact day the campaign launches. Normalize query parameters at the edge.
The cache key excludes something it should not. A response that varies by
Accept-Language, by currency, or by a feature flag, without a matching Vary. The
first visitor’s variant is served to everybody until it expires.
Purge is assumed to be instant. It usually is not, it is rarely atomic across every site, and a deploy that swaps HTML and assets at the same moment can leave a visitor holding new HTML that references assets the edge has not seen. Fingerprint your assets and deploy them before the HTML that points at them, and the problem disappears.
Nobody is watching the hit rate. It is the single number that tells you whether any of this is working, and it should be on a dashboard next to your error rate. A hit rate that quietly drops from 95% to 60% after a release is your origin taking eight times the traffic it did yesterday.
The short version
A CDN moves three round trips from thousands of kilometers to tens, and it answers repeated requests without waking your origin. The first benefit applies to everyone whose users are far away. The second applies only to responses that can be shared, and that is a property of your application, not of your CDN.
Get the cache key right, split fingerprinted assets from short-lived HTML, turn on
stale-while-revalidate, and put the hit rate on a dashboard. That is most of the
value, and it is available on every provider in the list.