CDNs & Caching cheat sheet

A scannable CDNs & Caching reference: 32 short snippets across 13 topics, each linking back to the lesson it came from.

At a glance

TopicWhat it covers
How a CDN serves your contentA CDN is a network of reverse proxies spread across many locations. DNS or anycast routing sends each visitor to thelesson
Cache headers and cache keysProvider dashboards often override these headers with their own TTL settings. Find out which one wins before debugginglesson
Invalidation, purging and what not to cacheThere are three ways to invalidate: purge by URL, purge by tag or surrogate key, and purge everything. Purging one URLlesson
Choosing a CDN and connecting an originThe Host header is where most first deployments fail. The CDN forwards a Host value, the origin's virtual host does notlesson
Cache hierarchy: browser, edge and origin cachesHow the three cache layers interact, why a browser hit still helps, the revalidation flow, proxy caches, and designinglesson
Static asset optimisation: compression and modern imagesBrotli and gzip negotiation, minification and bundling decisions, WebP and AVIF with fallbacks, responsive imagelesson
Image CDNs and on-the-fly transformationOrigin-based versus URL-based transformation, resize and quality parameters, format negotiation, caching generatedlesson
Signed URLs, token authentication and hotlink protectionA signed request carries a signature computed from a shared secret and some part of the request - usually the path, anlesson
Security at the edge: WAF, DDoS and bot managementManaged and custom WAF rules, rate limiting at the edge, how DDoS absorption works, bot scoring and challenges, andlesson
Video and large file deliveryRange requests and seeking, segment-based streaming, adaptive bitrate basics, how large files are cached and evictedlesson
Measuring CDN performance and debugging cache issuesReading cache status headers, hit ratio and origin offload, TTFB and real user monitoring, and a step-by-step workflowlesson
Multi-CDN, failover and cost optimisationA second CDN doubles the operational surface: two configurations, two log formats, two purge APIs and two sets of ruleslesson
Edge architecture: personalisation and A/B testing at the edgeA page cannot be both cached for everyone and personalised for one person. The solution is structural: cache the partslesson

Quick snippets

How a CDN serves your content

Edge locations and the origin

# which layer answered, and was it a hit?
curl -sI https://example.com/assets/app.js | grep -i -E "cache-control|age|etag|x-cache|cf-cache-status"

# cf-cache-status: HIT | MISS | EXPIRED | REVALIDATED | DYNAMIC
# x-cache: Hit from cloudfront / Miss from cloudfront
# age: seconds this copy has been in the cache

What a CDN also gives you

# origin: tell the edge how long it may keep the response
location /assets/ {
  add_header Cache-Control "public, max-age=31536000, immutable";
}
location / {
  add_header Cache-Control "public, max-age=0, s-maxage=3600, stale-while-revalidate=60";
}

# and make sure a wrong host header cannot poison the cache
if ($host != "example.com") { return 444; }

Full lesson: How a CDN serves your content →

Cache headers and cache keys

The headers that matter at the edge

# an asset whose name changes with its content
Cache-Control: public, max-age=31536000, immutable

# HTML: browsers revalidate, the edge holds a copy for an hour
Cache-Control: public, max-age=0, s-maxage=3600, stale-while-revalidate=60

# anything user-specific
Cache-Control: private, no-store

# API data that may be briefly stale under load
Cache-Control: public, max-age=30, s-maxage=300, stale-while-revalidate=120

What makes up a cache key

Vary: Accept-Encoding, Accept-Language
Cache-Key: host + path + country + normalized-query

# in the CDN configuration, the equivalent rules look like:
#   ignore query string params: utm_*, gclid, fbclid, msclkid
#   key on: path, country (only for /pricing/*)
#   never key on: cookie _session for /assets/*

Verifying from the outside

for i in 1 2; do
  curl -sI "https://example.com/?utm_source=test" | grep -i -E "^(age|cache-control|x-cache)"
  sleep 2
done

# strip the tracking parameter and confirm the same cached object
curl -sI "https://example.com/" | grep -i -E "^(age|x-cache)"

Full lesson: Cache headers and cache keys →

Invalidation, purging and what not to cache

Getting rid of a stale copy

# purge one URL (provider-specific API shape)
curl -X POST "https://api.cdn.example/purge" \
  -H "Authorization: Bearer $CDN_TOKEN" \
  -d '{"urls":["https://example.com/assets/app.js"]}'

# tag the response at the origin, then purge by tag after a content update
# Surrogate-Key: article-1042 list-home
curl -X POST "https://api.cdn.example/purge" \
  -H "Authorization: Bearer $CDN_TOKEN" \
  -d '{"tags":["article-1042"]}'

What must not be cached

# origin: be explicit rather than relying on defaults
location /account/ {
  add_header Cache-Control "private, no-store" always;
  add_header X-Robots-Tag "noindex" always;
}

# edge: strip cookies from cacheable assets so they are not keyed on them
location /assets/ {
  proxy_hide_header Set-Cookie;
  add_header Cache-Control "public, max-age=31536000, immutable" always;
}

Full lesson: Invalidation, purging and what not to cache →

Choosing a CDN and connecting an origin

Connecting the origin

# verify the CDN is in front and the origin is not exposed
curl -sI https://cdn-stage.example.com/ | grep -iE "server|via|x-cache|cf-cache-status|age"

# verify the origin responds correctly when addressed directly
curl -sI https://origin.example.com/ -H "Host: cdn-stage.example.com" | head -5

# confirm which certificate the edge presents
echo | openssl s_client -connect cdn-stage.example.com:443 -servername cdn-stage.example.com 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

Validating the first deployment

# first request (miss) and second request (hit) on the same asset
curl -sI https://cdn-stage.example.com/static/app.js | grep -iE "age|cache-control|x-cache"
curl -sI https://cdn-stage.example.com/static/app.js | grep -iE "age|cache-control|x-cache"

# a 404 must not be cached as a success
curl -sI https://cdn-stage.example.com/does-not-exist | head -3

Full lesson: Choosing a CDN and connecting an origin →

Cache hierarchy: browser, edge and origin caches

Three layers, one request

What a request looks like at each step

  1. browser has a fresh copy            -> no request leaves the device
  2. browser copy is stale               -> conditional request with If-None-Match
                                            or If-Modified-Since
  3. edge has a fresh copy               -> 200 from the edge, Age: n
  4. edge copy is stale                  -> revalidate with the shield or origin
  5. shield has a fresh copy             -> 200 from the shield
  6. nothing has it                      -> origin renders, everyone stores it

Revalidation and stale serving

# HTML that must be current but should not block the user
Cache-Control: public, max-age=0, s-maxage=600, stale-while-revalidate=86400, stale-if-error=3600

# a fingerprinted asset that will never change
Cache-Control: public, max-age=31536000, immutable

# an API response that is safe for a minute at the edge, never in the browser
Cache-Control: private, no-store, s-maxage=60

# an authenticated response that must never be shared
Cache-Control: private, no-store
Vary: Authorization, Cookie

Revalidation and stale serving

# see what a second request looks like
curl -sI https://example.com/ | grep -iE "cache-control|age|etag|vary"
curl -sI https://example.com/ | grep -iE "cache-control|age|etag|vary"

# force a revalidation and watch for a 304
curl -sI -H 'If-None-Match: "<etag-from-above>"' https://example.com/ | head -3

Full lesson: Cache hierarchy: browser, edge and origin caches →

Static asset optimisation: compression and modern images

Compression

# Brotli when the client offers it, gzip as the fallback
brotli on;
brotli_comp_level 5;
brotli_types text/plain text/css application/javascript application/json image/svg+xml;

gzip on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_vary on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;

Compression

# what is actually being served, and how large it really is
curl -sI -H "Accept-Encoding: br, gzip" https://example.com/static/app.js \
  | grep -iE "content-encoding|content-length|vary|cache-control"

# measure the transfer size, not the decoded length
curl -s -o /dev/null -w "encoded bytes: %{size_download}\n" \
  -H "Accept-Encoding: br" https://example.com/static/app.js

Modern images

<!-- modern formats with an explicit fallback -->
<picture>
  <source type="image/avif" srcset="/img/hero-800.avif 800w, /img/hero-1600.avif 1600w"
          sizes="(max-width: 600px) 100vw, 50vw" />
  <source type="image/webp" srcset="/img/hero-800.webp 800w, /img/hero-1600.webp 1600w"
          sizes="(max-width: 600px) 100vw, 50vw" />
  <img src="/img/hero-800.jpg" width="1600" height="900" loading="lazy"
       decoding="async" alt="Dashboard overview" />
</picture>

Full lesson: Static asset optimisation: compression and modern images →

Image CDNs and on-the-fly transformation

Two ways to transform

<!-- URL-based transformation: the variant is described in the URL -->
<img src="https://cdn.example.com/img/hero.jpg?w=800&q=75&fm=webp"
     srcset="https://cdn.example.com/img/hero.jpg?w=400&q=75&fm=webp 400w,
             https://cdn.example.com/img/hero.jpg?w=800&q=75&fm=webp 800w,
             https://cdn.example.com/img/hero.jpg?w=1600&q=75&fm=webp 1600w"
     sizes="(max-width: 600px) 100vw, 60vw"
     width="1600" height="900" alt="Order dashboard" />

Caching generated variants

# a transformed variant is deterministic, so it can be cached hard
Cache-Control: public, max-age=31536000, immutable

# the transformation response must vary on what actually changes it
Vary: Accept

# and a limit on the number of generated variants avoids runaway cost
# e.g. allow w in {200,400,800,1200,1600}, q fixed at 75

Caching generated variants

# is a variant cached after the first request?
curl -sI "https://cdn.example.com/img/hero.jpg?w=800&q=75" | grep -iE "age|cache-control|content-type"
curl -sI "https://cdn.example.com/img/hero.jpg?w=800&q=75" | grep -iE "age|cache-control|content-type"

# does the CDN vary by Accept?
curl -sI -H "Accept: image/webp" "https://cdn.example.com/img/hero.jpg?w=800" \
  | grep -iE "content-type|vary"

Full lesson: Image CDNs and on-the-fly transformation →

Signed URLs, token authentication and hotlink protection

Signing without destroying the cache

The cache-key problem

  URL /v/a.mp4?expires=1700000000&sig=abc   -> one cached object
  URL /v/a.mp4?expires=1700000060&sig=def   -> a different cached object

  Sixty seconds later the same file is cached again, and the hit ratio collapses.

  Fix: put the authorisation in a cookie and keep the URL stable,
  or configure the CDN to exclude the signature parameters from the cache key.
  Excluding them makes the object shared, so verify the token on every request.

Hotlinking and what signing cannot do

Referrer-based hotlink protection

  Allow when the Referer is one of your own hostnames, or absent.
  Block when it is another site.

  Reality check
    a Referer can be spoofed by a client, so this stops casual embedding only
    an empty Referer must be allowed, or users with strict privacy settings break
    search engines and link previews send no Referer - check before enabling

Full lesson: Signed URLs, token authentication and hotlink protection →

Security at the edge: WAF, DDoS and bot management

WAF rules that help

A rollout order that does not break the site

  1. enable managed rules in log-only mode for a week
  2. read the log: what would have been blocked, and was any of it real?
  3. enable the rules with no false positives, in block mode
  4. add rate limits on login, signup, search and any expensive endpoint
  5. add a challenge for low bot scores on those endpoints only
  6. keep an allow list for your own monitors and your payment provider's
     webhooks - a blocked webhook looks like a payment failure

DDoS absorption

Origin protection that survives an attack

  allow only the CDN address ranges, not the whole internet
  require a shared secret header that the CDN adds and the origin verifies
  set a low connection limit per source at the firewall
  cache aggressively at the edge so a flood of reads never reaches the origin
  alarm on origin request rate, not only on error rate

Tuning without collateral damage

# a crawler must be verified by reverse DNS, never by user agent string
dig -x 66.249.66.1 +short          # reverse lookup the claimed crawler address
dig +short googlebot.example.com   # forward confirm it resolves back to the same range

# and a synthetic check that a real user path still works after a rule change
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/login
curl -s -o /dev/null -w "%{http_code}\n" -H "User-Agent: $UA" https://example.com/

Full lesson: Security at the edge: WAF, DDoS and bot management →

Video and large file delivery

Range requests

# the client asks for a byte range
curl -sI -H "Range: bytes=0-1023" https://cdn.example.com/v/talk.mp4 \
  | grep -iE "content-range|accept-ranges|content-length|age"

# a 206 response means the range was honoured
# a 200 with the full length means the server ignored the range

Range requests

Response headers for a seekable, cacheable file

  Accept-Ranges: bytes
  Content-Length: 52428800
  Cache-Control: public, max-age=31536000, immutable
  Content-Type: video/mp4

The filename should contain a hash. A 50 MB file with a stable URL cannot be
cached for a year, because you can never change it.

Segmented streaming

Adaptive bitrate in one paragraph

  The video is encoded several times at different resolutions and bitrates and
  cut into short segments of two to six seconds. The playlist lists the renditions.
  The player measures throughput and buffer level and switches rendition between
  segments, so a slow connection drops to a lower quality instead of stalling.

  Cache consequences
    many small segment files, all immutable and cacheable forever
    playlists that change and need a short lifetime
    the cost is dominated by request count, not by file size

Full lesson: Video and large file delivery →

Measuring CDN performance and debugging cache issues

Reading the response

# one command that shows the whole story
curl -sI https://example.com/static/app.js \
  | grep -iE "age|cache-control|etag|vary|via|server|x-cache|content-encoding"

# and a loop that shows a miss followed by a hit
for i in 1 2 3; do
  curl -sI https://example.com/static/app.js | grep -iE "^age|x-cache"
  sleep 1
done

A miss debugging workflow

# does a stray cookie header break caching?
curl -sI https://example.com/static/app.js | grep -i "set-cookie"

# is the cache key split by a query parameter?
curl -sI "https://example.com/static/app.js?utm_source=news" | grep -iE "age|x-cache"
curl -sI "https://example.com/static/app.js" | grep -iE "age|x-cache"

# and from another location, expecting an independent cache
curl -sI https://example.com/static/app.js -H "Host: example.com" --resolve example.com:443:203.0.113.9 \
  | grep -iE "age|x-cache"

Full lesson: Measuring CDN performance and debugging cache issues →

Multi-CDN, failover and cost optimisation

Steering traffic

DNS steering that actually fails over

  primary    example.com  ->  primary CDN CNAME, TTL 60
  check      synthetic request every 30 s, from several regions
  threshold  three consecutive failures before switching
  switch     replace the answer to the secondary CNAME
  verify     confirm the new answer from two resolvers
  switch back only after the primary has been stable for 30 minutes

  Remember: a client that resolved just before the switch keeps using the
  old answer for the full TTL, so plan for a minute of mixed traffic.

Cost control and log unification

A single log schema for both providers

  time, cdn, pop, cache_status, url, status, bytes, ttfb_ms,
  client_country, user_agent_family, referer_host

With that, you can answer:
  what share of requests does each provider serve?
  what is the hit ratio for static assets per provider?
  what is the cost per GB after normalising the log volume?
  which provider is slower in the region that matters?

Full lesson: Multi-CDN, failover and cost optimisation →

Edge architecture: personalisation and A/B testing at the edge

Cached shell plus fragments

<!-- the shell is cached at the edge for everyone -->
<main>
  <h1>Your recommendations</h1>

  <!-- the fragment is fetched separately, with the user's credentials -->
  <div id="recs"
       hx-get="/api/recommendations"
       hx-trigger="load"
       hx-swap="innerHTML">
    <p class="skeleton">Loading recommendations...</p>
  </div>
</main>

Cached shell plus fragments

# the shell: cacheable, shared, fast
Cache-Control: public, s-maxage=300, stale-while-revalidate=86400

# the fragment: never shared, short
Cache-Control: private, no-store

Full lesson: Edge architecture: personalisation and A/B testing at the edge →

FAQ

Is this CDNs & Caching cheat sheet free to use?
Yes. No sign-up and no tracking: the page is static, every example is on the page itself, and you can print it or save it as a one-page reference.
Where do the examples come from?
Every snippet is taken from the 13 lessons of the CDNs & Caching course on this site, and each section links back to the lesson it was pulled from.
How do I go deeper than a cheat sheet?
Open the full CDNs & Caching course — it carries the worked explanations, the edge cases and the exercises behind every line here.

SEO Basics Domains & DNS Web Hosting Accessibility

Last refreshed 2026-09-27.