CDN Architecture
On this page
- Beating the Speed of Light
- How a Request Flows (the Pull Model)
- Getting Users to the Nearest Edge
- Controlling What the Edge Caches
- Verifying Hits and Misses
- How CDN Caching Fails in Practice
- The Origin Shield
- Invalidation: Fingerprints First, Purges Last
- Benefits Beyond Latency
- What Not to Put Behind a CDN Cache
- Practice
Beating the Speed of Light#
No amount of server tuning beats physics. Light in optical fibre travels at roughly 200,000 km per second, about 5 microseconds per kilometre. New York to Sydney is around 16,000 km in a straight line and considerably more along real cable routes, so a round trip takes 200 ms or more before your server does any work. A page that needs several sequential round trips (DNS, TCP, TLS, HTML, then CSS and scripts) pays that cost again and again.
A Content Delivery Network (CDN) moves copies of your content closer to users. It runs caching servers, called edge nodes or points of presence (PoPs), in many cities. Users connect to a nearby edge, which serves from its cache when it can and fetches from your origin server when it cannot.
How a Request Flows (the Pull Model)#
- Setup. You point
static.example.comat the CDN, usually with a DNS CNAME, and tell the CDN where your origin lives. - Cache miss. A user in Sydney requests
/logo.png. They reach the Sydney edge, which does not have the file yet. The edge fetches it from your origin in New York, stores it, and returns it. This first request is slow. - Cache hit. The next Sydney user gets the stored copy from the local edge in a few milliseconds. Your origin never sees the request.
Even on a miss, the CDN usually helps. The user's TCP and TLS handshakes finish against the nearby edge, and the edge reuses warm, long-lived connections to the origin, so only one long-haul round trip is paid instead of several.
In the push model you upload content to the CDN's storage ahead of time. It suits large video libraries and launch-day assets that must be warm everywhere from the first request. Most sites use pull.
Getting Users to the Nearest Edge#
CDNs steer users in two main ways, often combined:
- Anycast: many PoPs announce the same IP prefix over BGP, and internet routing delivers each user's packets to the topologically nearest one. If a PoP fails, its route is withdrawn and traffic shifts to the next nearest automatically. The routing lesson explains why this works.
- DNS-based steering: the CDN's authoritative DNS returns a different edge IP depending on where the query comes from (usually the location of the user's resolver). This allows finer control, such as taking a PoP out of rotation for maintenance, but depends on the resolver being near the user.
Controlling What the Edge Caches#
A CDN decides what to store mostly from your origin's HTTP headers, so caching behaviour is something you configure at the origin. The HTTP caching lesson covers the headers in full; these are the patterns that matter at the edge:
# Fingerprinted static asset (main.a7f2b9.css): cache for a year, never revalidate
Cache-Control: public, max-age=31536000, immutable
# HTML page: browsers always revalidate, the CDN may keep it 5 minutes,
# and may serve a stale copy for 60 s while it refetches in the background
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=60
# Per-user API response: never store in a shared cache
Cache-Control: private, no-store
s-maxage applies only to shared caches such as CDNs, which lets you cache HTML at the edge while browsers always check back. stale-while-revalidate and stale-if-error let the edge answer instantly from a slightly old copy while it refreshes, or keep serving when the origin is down.
The cache key determines which requests count as "the same object". By default it is typically the host plus the full URL including the query string. Headers named in Vary are added to it.
Verifying Hits and Misses#
Don't assume the CDN is caching. Check it. CDNs add response headers that report cache status, for example CF-Cache-Status (Cloudflare), X-Cache (CloudFront and Fastly), and the standard Age header, which counts seconds since the object was stored at the edge. This script requests a URL on your own site twice and reports what came back:
import time
import urllib.request
URL = "https://static.example.com/assets/main.a7f2b9.css" # your own asset
STATUS_HEADERS = ["cf-cache-status", "x-cache", "age", "cache-control", "vary"]
def probe(url):
req = urllib.request.Request(url, method="HEAD",
headers={"User-Agent": "cdn-check/1.0"})
start = time.perf_counter()
with urllib.request.urlopen(req, timeout=10) as resp:
elapsed = (time.perf_counter() - start) * 1000
found = {h: resp.headers.get(h) for h in STATUS_HEADERS if resp.headers.get(h)}
return elapsed, found
for attempt in (1, 2):
ms, headers = probe(URL)
print(f"request {attempt}: {ms:6.1f} ms {headers}")
A healthy result looks like a MISS (or no Age) on the first request and a HIT with a growing Age on the second, with a clearly lower time. What the other results tell you:
- Always
MISS: the origin is sendingCache-Control: private,no-store, ormax-age=0withouts-maxage, or aSet-Cookieheader that the CDN treats as uncacheable. DYNAMICorBYPASS: a CDN rule or the file type excludes it from caching altogether.HITin one region andMISSin another: normal, since each PoP has its own cache.
The browser devtools Network tab shows the same headers for every asset on a page, which makes it quick to spot the ones that are not being cached.
How CDN Caching Fails in Practice#
- Low hit ratio from cache-key fragmentation. Tracking parameters (
?utm_source=...), random cache-busting query strings, orVary: User-Agentsplit one object into thousands of variants, each fetched from origin separately. Strip irrelevant query parameters from the key and avoid varying on high-cardinality headers. - Caching private data. An origin that forgets
Cache-Control: privateon an account page can have one user's page served to the next. Attackers exploit loose rules deliberately with web cache deception and poisoning, covered in the web cache poisoning lesson. Default dynamic routes to no-store and opt specific paths in. - Origin exposed directly. If attackers can find your origin IP (from old DNS records or mail headers), they bypass the CDN's caching and DDoS protection. Allow inbound traffic only from the CDN's published IP ranges, or require a secret header or mutual TLS from the CDN.
- Thundering herd on expiry. When a popular object expires everywhere at once, hundreds of PoPs miss together. Request collapsing and an origin shield (below) turn those into one origin fetch.
The Origin Shield#
Without extra layers, every PoP's miss goes to your origin, so one new object across 300 PoPs can mean 300 origin fetches. An origin shield is a designated mid-tier cache, usually in a region close to your origin: edges fetch through the shield, and only the shield talks to the origin. Combined with request collapsing (concurrent misses for the same key wait for a single fetch), this keeps origin load flat during traffic spikes and makes the origin's capacity planning predictable.
Invalidation: Fingerprints First, Purges Last#
Once an object is cached with a long TTL, changing the file at origin does nothing until the TTL expires. There are two ways to deal with that:
- Fingerprinted filenames. Build tools such as Vite and Webpack put a content hash in each filename (
main.a7f2b9.css). A new build produces a new URL, which the CDN has never seen, so users get the new file immediately and old files can be cached asimmutablefor a year. This is the default strategy for all static assets. - Purge APIs. Every CDN lets you remove objects by URL, and many support tags or prefixes. Purges are the right tool for HTML and for emergencies (a leaked file, a broken release), but they take time to propagate to every PoP and are often rate-limited. A deployment strategy that depends on purging every file is fragile.
For HTML, which cannot be fingerprinted because its URL is fixed, combine a short s-maxage with stale-while-revalidate and purge on deploy.
Benefits Beyond Latency#
- Lower egress cost. Cloud providers charge for outbound data. A 95% hit ratio means the origin serves only a small share of bytes, and CDN bandwidth is often cheaper per gigabyte.
- DDoS absorption. A large CDN spreads a volumetric attack across its whole network, and anycast naturally divides it among PoPs. See the DDoS lesson for how attacks and mitigations work.
- Availability. With
stale-if-error, the edge keeps serving cached pages while the origin is down or deploying. - TLS at the edge. The CDN terminates TLS near users, with modern protocol support, without changes to your origin.
What Not to Put Behind a CDN Cache#
Personalised responses, anything behind authentication, and write APIs should pass through uncached. You can still route them through the CDN for TLS termination, connection reuse, and DDoS protection, but mark them private, no-store and confirm with the probe script that the cache status reports a bypass.
Practice#
Pick one page on a site you control and list every asset it loads with its Cache-Control and cache-status headers. Fix any static asset that is not a HIT on the second load, give the HTML a short s-maxage with stale-while-revalidate, and measure the time to first byte from two distant regions before and after.
Check your understanding
4 quick questions on this lesson. Get them all right and the lesson is marked complete.