SevyDevy

Guide · System design

How a CDN Works: Push vs Pull, Cache Keys and Purges

28 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • A CDN (content delivery network) is a set of caching servers in many cities that keep copies of your files near your users. Most requests then never reach your own server, called the origin.
  • In a pull CDN, a nearby CDN server, called an edge, fetches a file from your origin the first time someone asks. In a push CDN, you upload files ahead of time.
  • The cache key is the label a CDN uses to decide which requests share one stored copy. Leave a per-viewer token, a code that proves a link is allowed, in it and almost every request misses the cache.
  • TTL (time to live) is how long a copy counts as fresh. Files that never change get a long TTL and a new name for each version. The few fixed addresses get a short TTL or a purge, a request to drop the old copy.
  • Beginners can stop after the purge section. Engineers then get a test edge on nginx 1.24.0, a web server that can act as a cache. Fixing its cache key cut 500 origin fetches to 1. They also get a gap, undocumented as of 19 October 2026. The stale-if-error header, a label sent with a file, lets caches serve old copies when the origin fails. On nginx 1.24.0 it did not cover error replies: the 500, 502, 503 or 504 codes a struggling origin sends back.

What goes wrong when everyone presses play at the same moment?

When thousands of people press play together, they all pull the same video from one place. The network link or the servers there run out of room, and viewers see a spinning wheel.

A CDN is the usual fix, but only when it is set up right. A badly set CDN still sends most requests back to your own server.

Picture a common setup. A streaming app records stage plays in Kerala and streams them to fans in Kerala, Bengaluru, the Gulf and Canada. Its servers sit in one data centre in Kochi. The setup is made up, but the problems are real.

For its biggest premiere, the team planned a Friday 8 pm release. About 40,000 people pressed play in the first ten minutes. Each stream needed about 3 megabits per second, which adds up to 120 gigabits per second.

The Kochi data centre had a 10 gigabit per second link. The team already paid for a CDN, yet viewers in Dubai and Toronto still saw a spinning wheel. The next morning, the logs showed three problems.

  • Only 31 percent of video requests were answered by the CDN. The rest went all the way to Kochi, even for the same piece of video.
  • A typo in the English subtitles was fixed at 9 pm. Viewers kept seeing the typo until the next evening.
  • The play's poster showed the old title card for hours after the team replaced the image.

Each problem has a different cause: the cache key, the TTL and the purge plan. This guide fixes them one at a time.

What is a CDN and how does it work?

A CDN is a network of servers, spread across many cities, that stores copies of your files close to your users and answers their requests for you. Your origin sends a file only when the CDN does not have it.

  • Origin: the server that holds the real, original files. Here, that is the storage and video servers in Kochi.
  • Edge server: a CDN server near users that keeps copies. A viewer in Dubai is served by an edge server in or near Dubai.
  • Cache hit: the edge already has a fresh copy and answers at once. Cache miss: it does not, so it must fetch the file first.
  • Hit ratio: the share of requests that are hits. A 31 percent hit ratio means about two in three requests were misses.
  • TTL (time to live): how many seconds a copy counts as fresh before the cache must check with the origin again.

How does a phone in Dubai find a server in Dubai? Many CDNs use DNS, the internet's phone book that turns a name into an address. The 2010 paper describing the Akamai network explains this step.

In that design, the mapping system turns the site's domain name into the address of a suitable edge server. The choice weighs how close each cluster is to the user, live loss and delay on the network, and how loaded and healthy each cluster is.

The same paper explains why closeness matters. The slow, crowded stretch is often the middle mile, where traffic crosses from one network to another at peering points. Serving from servers near users avoids most of it.

Follow the arrows from the viewer in Dubai. The shield is a middle cache, explained in the next section. Notice that the Kochi origin is the last stop, not the first.

How a video piece reaches a viewer in Dubai

The viewer talks to a nearby edge. Only misses travel further, first to the shield, then to the origin in Kochi.

Diagram connections:

  • Viewer's phone in Dubai to CDN mapping by DNS (Looks up the video domain)
  • CDN mapping by DNS to Edge server near Dubai (Answers with this edge)
  • Viewer's phone in Dubai to Edge server near Dubai (Asks for each segment)
  • Edge server near Dubai to Shield cache in Mumbai (Only on a miss)
  • Shield cache in Mumbai to Origin in Kochi (Only on a miss)
  • Origin in Kochi to Video storage (Reads the file)
  • Viewer's phone in Dubai: Plays the premiere in the app
  • CDN mapping by DNS: Turns the video domain into a nearby edge
  • Edge server near Dubai: Keeps popular video pieces close by
  • Shield cache in Mumbai: One parent cache shared by many edges
  • Origin in Kochi: Sends files only when the shield misses
  • Video storage: Every piece of every recorded play

A streamed play is not one big file. The app uses HLS (HTTP Live Streaming), where a video is cut into short pieces called segments. A small text file called a playlist lists the segments, and the player fetches them one by one over plain HTTP.

That suits a CDN well. Thousands of viewers fetch the same segments, and a recorded segment never changes once made. The HLS specification, RFC 8216, says the format works with large-scale HTTP caching systems.

What happens on a CDN cache hit and a cache miss?

On a cache hit, the edge server answers from its own copy, without a trip to the origin. On a cache miss, the edge fetches the file from a parent cache or the origin, sends it on, and usually keeps a copy for the next viewer.

Follow one request for segment 214 of the premiere. Watch the question box: everything after it depends on one yes or no.

One segment request at an edge server

A hit ends at the edge. A miss walks up to the shield, and to the origin only if the shield misses too.

Diagram connections:

  • Phone asks for segment 214 to Edge builds the cache key
  • Edge builds the cache key to Fresh copy for this key?
  • Fresh copy for this key? to Serve the edge copy (Yes, a hit)
  • Fresh copy for this key? to Ask the shield cache (No, a miss)
  • Ask the shield cache to Fetch from the origin (Shield misses too)
  • Ask the shield cache to Keep a copy at the edge (Shield has it)
  • Fetch from the origin to Keep a copy at the edge (File flows back)
  • Keep a copy at the edge to Segment plays
  • Serve the edge copy to Segment plays
  • Phone asks for segment 214: One short piece of the play
  • Edge builds the cache key: Host, path and the query parts that matter
  • Fresh copy for this key?: Looks it up in the edge's own storage
  • Serve the edge copy: Fastest path, the origin never hears of it
  • Ask the shield cache: The Mumbai parent may already have it
  • Fetch from the origin: Only when the shield misses too
  • Keep a copy at the edge: Stays fresh until its TTL runs out
  • Segment plays: The viewer never sees which path it took

The middle cache is called an origin shield or a tiered cache. The Akamai paper calls the idea tiered distribution. When an edge cluster misses, it asks a parent cluster instead of going straight to the origin.

Offload is the share of requests kept away from the origin. The paper says tiered distribution typically gave offload in the high 90s, in percent, even for customers with very large libraries. That is 2010 data from one network, not a promise.

Now say 200 edges miss on segment 214 at the same moment. Without a shield, Kochi gets 200 requests. With a shield, all 200 go to one parent instead.

That alone is not enough. The shield must also merge matching requests that arrive together into one origin fetch, which is called request collapsing. In the engineer section, one nginx setting cut 200 such requests to 1.

What is the difference between a push CDN and a pull CDN?

In a pull CDN, the edge fetches a file from your origin the first time a user asks for it. In a push CDN, you upload files to the CDN's own storage before anyone asks, so no request has to reach your origin.

Think of ice-cream carts along Fort Kochi beach, all stocked from one cold store. Some carts are loaded every morning with the flavours that always sell. That is push.

Other carts carry a few tubs and send a runner back to the cold store when a flavour runs out. That is pull. The cold store is the origin, each cart is an edge, and the runner's trip is a cache miss.

The morning-loaded cart is ready for the rush, but someone guessed the flavours, and unsold tubs waste freezer space. The pull cart carries only what sells, but its first buyer waits.

Where the picture breaks: a tub is gone once sold, but one cached file can be served to millions of viewers. A cart also cannot ask a nearby cart, while an edge can ask a shield.

Push and pull CDNs compared for the streaming app
Push CDNPull CDN
Who moves the file to the CDNYou upload it, before anyone asksThe edge fetches it on the first miss
First viewer's experienceNo trip to your origin, though an edge may still fetch from CDN storageA miss all the way to your origin, so a slower start
Origin needs to stay onlineLess, the files already live in the CDNYes, every miss goes back to it
Storage you pay forEverything you pushed, used or notOnly what viewers actually asked for
Work for your teamAn upload step in every releaseSet the origin once, then mostly nothing
Changing a fileUpload the new one yourselfWait for the TTL, rename or purge
Best fit hereA premiere everyone will watch tonightThe back catalogue of 600 older plays

The team had used pure pull. At 8 pm, every edge missed on the first segments of the premiere at once, and every runner ran to the cold store together.

For the next premiere, the team mixes the two. They warm the cache, meaning they load chosen files in advance, by requesting the first ten minutes of the play through the shield an hour early.

Warming every edge is harder. A warm-up request usually reaches only the edge that the CDN maps the sender to, unless your CDN offers a preload feature. Warming the shield means each edge's first miss is answered by the shield, not by the origin.

The 600 older plays stay on pull. Most are watched a few times a week, and pushing all of them to every edge would fill storage with files nobody asks for.

What is a CDN cache key?

A cache key is the label a cache builds from each request to decide which stored copy to use. Two requests with the same key share one copy, and two requests with different keys never do.

The HTTP caching standard, RFC 9111, says the key is made of at least the request method and the target URI. The method is the action asked for, such as GET to read a file. The URI is the full address, including the part after the question mark, called the query string.

RFC 9111 also notes that many caches store only GET responses, so they key on the URI alone. Many CDNs let you choose which parts of the URI count.

This is where the 31 percent hit ratio came from. To stop people sharing video links, the app signed every segment URL. Each app viewer got a URL with their own token and expiry time in the query string.

So segment 214 had thousands of different URLs, one per app viewer. To the CDN, each looked like a different file. Almost every app request was a miss and went to Kochi. Only the smaller web audience got hits, because its player protected links another way and used plain URLs.

The fix is to let the edge check the token, then leave it out of the key. Real CDNs do this in their settings. Here is the same logic in Python, run on Python 3.13.

python
from urllib.parse import urlsplit, parse_qsl, urlencode

# Query parts that change per viewer but not the file itself
IGNORED = {"token", "expires", "utm_source", "utm_campaign"}

def cache_key(method, url):
    parts = urlsplit(url)
    keep = sorted((k, v) for k, v in parse_qsl(parts.query)
                  if k not in IGNORED)
    return f"{method} {parts.netloc.lower()}{parts.path}?{urlencode(keep)}"

a = "https://media.example.com/plays/seg214.ts?token=ab12&q=720p"
b = "https://MEDIA.example.com/plays/seg214.ts?q=720p&token=zz99"
c = "https://media.example.com/plays/seg214.ts?q=1080p&token=ab12"
print(cache_key("GET", a) == cache_key("GET", b))   # True
print(cache_key("GET", a) == cache_key("GET", c))   # False

What just happened? The IGNORED set lists query parts that differ per viewer but never change the file. The sorted call puts the remaining parts in a fixed order, so q=720p&a=1 and a=1&q=720p become one key.

The quality setting q must stay in the key, because a 720p segment and a 1080p segment are different files. That is why the last line prints False.

A response can also widen the key with the Vary header. A header is a named line of extra information sent with every request and response. Vary names request headers that must match too, such as Accept-Language for a page that comes in Malayalam and English. Every extra variant splits your hit ratio, so use it sparingly.

In a replay of the premiere's traffic, this one change raised the hit ratio on video segments from 31 percent to 96 percent (made up). The share of requests reaching Kochi fell from 69 percent to 4 percent.

How do you control how long a CDN keeps a file?

You control it mostly with the Cache-Control header that your origin sends with each file. Its max-age value says how many seconds the file stays fresh. A CDN follows it unless you override it in the CDN's settings.

Browsers read Cache-Control too. Often you want the CDN to keep a file longer than phones do, because you can purge the CDN but not a phone. The s-maxage value does exactly this.

RFC 9111 says s-maxage applies only to shared caches, like a CDN, where it overrides max-age. It also says a shared cache must not reuse a stale copy under s-maxage until the origin confirms it.

The subtitle file had a one-day TTL, which explains the typo that lasted until the next evening. The team now sorts every file type by one question: can this exact URL ever change?

The streaming app's TTL plan by file type (made up)
File typeCan the same URL change?CDN TTLWhy
Video segmentsNeverOne yearA recorded segment is fixed once it is made
Posters and subtitlesNo, a new version gets a new nameOne yearThe name changes whenever the content does
Recorded play's playlistRarelyFive minutesMust point to the newest subtitle and poster names
Live festival playlistEvery few secondsAbout half a segment's lengthNew segments keep getting added
Home page list of playsOften60 seconds, plus stale servingFresh enough, and each cache asks the origin about once a minute
My Account and watch historyPer userNot cached at the CDNPrivate data must never be shared

For files that get a new name per version, add the immutable directive from RFC 8246: Cache-Control: public, max-age=31536000, immutable. That is one year in seconds, and it tells browsers not to re-check the file on a normal reload while it is fresh.

The live row matters for next month's theatre festival, which the app will stream live. Each HLS playlist sets a target duration, the longest a segment may be.

RFC 8216 says a live playlist gets a new version between half and one and a half target durations after the last one. Cache it longer and viewers stall, waiting for segments the CDN does not know exist yet.

Two more directives, defined in RFC 5861 in 2010, help when a file goes stale. They hide a slow or broken origin from viewers.

  • stale-while-revalidate=30 lets a cache serve the old copy for up to 30 seconds after it goes stale, while it fetches a fresh one in the background.
  • stale-if-error=600 lets a cache serve the old copy for up to 600 seconds after it goes stale if the origin fails. RFC 5861 counts anything that would produce a 500, 502, 503 or 504 status.

Here is the home page list from the origin: Cache-Control: public, max-age=60, stale-while-revalidate=30, stale-if-error=600. It stays fresh for 60 seconds. After that, a cache may keep serving it while it refreshes, or while Kochi is failing.

Use max-age here, not s-maxage. On paper, s-maxage forbids stale copies at the CDN. The engineer section shows that nginx does not follow that rule, so test your own CDN.

How do you invalidate or purge a CDN cache?

You either give the changed file a new URL, so nobody asks for the old copy again, or you ask the CDN to drop the old copy. Many teams rename files and purge only the few URLs that must stay fixed.

Way 1: a new name for every new version

This is called versioning or cache busting. You put a short fingerprint of the file's content into its name. When the content changes, the name changes, and the old copy just ages out unused.

python
import hashlib

def versioned_name(path, data):
    digest = hashlib.sha256(data).hexdigest()[:10]   # fingerprint of content
    stem, _, ext = path.rpartition(".")
    return f"{stem}.{digest}.{ext}"

old = b"1\n00:00:01.000 --> 00:00:04.000\nThe backwaters are quite tonight.\n"
fixed = b"1\n00:00:01.000 --> 00:00:04.000\nThe backwaters are quiet tonight.\n"
print(versioned_name("subs/play-1042.en.vtt", old))
print(versioned_name("subs/play-1042.en.vtt", fixed))
# Two different names: one wrong letter changed the fingerprint

The line that matters is the digest. SHA-256 turns the file's bytes into a fingerprint, and changing even one letter gives a different one. Ten hex characters (digits 0 to 9 and letters a to f) keep 40 bits, plenty to tell versions of one file apart.

Because the name now changes with the content, the file can get a one-year TTL safely. The catch is that something must point to the new name. Here that is the play's playlist, and the playlist's own URL never changes.

Way 2: purge the old copy

A purge is a request to the CDN to stop using a cached copy, so the next request becomes a miss and fetches a fresh one. CDNs usually let you purge one URL, a group of files by tag, or everything.

  • Purge by URL: removes one address. Simple, but you must know every exact URL, including each quality level.
  • Purge by tag: Fastly calls these surrogate keys. Your origin adds a Surrogate-Key header listing tags, such as play-1042, and one purge clears every file with that tag.
  • Purge everything: clears all your files at once. Every edge then misses together, which can flood the origin just like a premiere does.

Fastly's documentation adds two details. The CDN removes the Surrogate-Key header before the response reaches viewers. A soft purge marks an object as stale instead of removing it, so it can still be served if the origin is down.

Purges also have limits. As of 19 October 2026, Fastly allows an average of 100,000 URL and key purges per hour per customer, and counts purge-all calls against its API rate limits. An API is the interface your code uses to send commands, such as a purge, to the CDN.

Here is how a subtitle fix now reaches viewers. Start at the fix and look at where the two paths meet: the playlist purge.

How a subtitle fix reaches every viewer

The fixed file gets a new name. Only the playlist, whose URL never changes, needs a purge.

Diagram connections:

  • An engineer fixes a typo to Can the URL change?
  • Can the URL change? to Upload under a new name (Yes, it is a file)
  • Can the URL change? to Purge tag play-1042 (No, fixed URL)
  • Upload under a new name to Update the play's playlist (New name to list)
  • Update the play's playlist to Purge tag play-1042 (Playlist URL never changes)
  • Purge tag play-1042 to Edges fetch fresh copies
  • Edges fetch fresh copies to Viewers see the fix
  • An engineer fixes a typo: English subtitles for the premiere
  • Can the URL change?: Asked for every file that changed
  • Upload under a new name: Content fingerprint in the file name
  • Update the play's playlist: Now lists the new subtitle name
  • Purge tag play-1042: One request, sent to the CDN
  • Edges fetch fresh copies: The next request for the playlist misses
  • Viewers see the fix: Old subtitle copies age out unused

The old poster had the same cause: a new image under the old name, with a long TTL and no purge. Posters now get fingerprinted names too.

How do you check whether a CDN served a file from cache?

Request the file twice and read the response headers. Look for an Age header and for the CDN's cache status header.

bash
curl -s -o /dev/null -D - https://media.example.com/plays/seg214.ts | grep -i -E '^(age|cache-status|x-cache|cf-cache-status):'
# Run it twice. Example of a hit (header names and values vary by CDN):
#   age: 37                      (seconds since the origin made this copy)
#   x-cache: HIT
#   cache-status: ExampleCache; hit; ttl=376
  • Age: RFC 9111 defines it as the cache's estimate, in seconds, of the time since the origin made or confirmed this copy. If Age is present, the origin did not make or confirm this response for your request.
  • No Age header proves nothing. RFC 9111 says its absence does not mean the origin was contacted, and nginx 1.24.0 sent no Age header on hits in our test.
  • Cache-Status: a standard header from RFC 9211 (2022). Each cache in the chain adds an entry, such as hit or fwd=uri-miss, with the cache nearest to you listed last.
  • Vendor headers: Fastly sends X-Cache, which can list one entry per server when shielding is on. Cloudflare sends CF-Cache-Status with values such as HIT, MISS, EXPIRED and STALE.

For the hit ratio itself, use your CDN's logs or dashboard, split by file type. A sudden fall often means someone changed the cache key or a response header.

How do you build and test a CDN edge with nginx?

You can run one edge on a laptop with nginx, a web server that can also act as a cache. It is not a CDN, but it applies the same rules to one box, so you can test them.

We ran this file as a normal user, not root, on nginx 1.24.0, the version Ubuntu 24.04 ships. It checks the signed link, keys on host, path and quality, collapses misses and serves stale copies.

nginx
# edge.conf: a one-box test edge for nginx 1.24.0
# Run from this folder as your normal user: nginx -p "$PWD" -c edge.conf
pid edge.pid;
error_log stderr;    # log to the terminal, not a root-owned file
events {}
http {
  access_log off;    # same reason: no root-owned access log
  proxy_cache_path cache keys_zone=edge:10m max_size=1g;
  proxy_temp_path tmp;
  server {
    listen 127.0.0.1:8081;
    location /plays/ {
      # 1. Check the signed link on every request, hits included
      secure_link $arg_token,$arg_expires;
      secure_link_md5 "$secure_link_expires$uri SECRET";
      if ($secure_link = "")  { return 403; }
      if ($secure_link = "0") { return 410; }
      # 2. Cache key: host, path and quality only, never the token
      proxy_cache edge;
      proxy_cache_key "$host$uri$is_args$arg_q";
      proxy_cache_valid 200 10m;
      # 3. Send one request to the origin per missing file
      proxy_cache_lock on;
      # 4. Serve the old copy while refreshing, or if the origin fails
      proxy_cache_background_update on;
      proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
      add_header X-Cache-Status $upstream_cache_status always;
      proxy_pass http://127.0.0.1:8080;
    }
  }
}

The nginx package in Ubuntu 24.04 includes the secure_link module used here. A build from source needs the --with-http_secure_link_module option. The module uses MD5, an old fingerprint method that is no longer considered secure. That is fine for a test; for production, use your CDN's signed URL feature.

For a test origin, make a folder with plays/seg214.ts inside, any file will do. Run python3 -m http.server 8080 in that folder, in a second terminal. Then sign a link and request it twice.

bash
# Sign a link that expires in one hour (same rule as secure_link_md5)
E=$(( $(date +%s) + 3600 ))
T=$(echo -n "$E/plays/seg214.ts SECRET" | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =)
curl -s -o /dev/null -D - "http://127.0.0.1:8081/plays/seg214.ts?q=720p&expires=$E&token=$T" | grep -i x-cache-status
# First run prints:  X-Cache-Status: MISS
# Run it again:      X-Cache-Status: HIT
curl -s -o /dev/null -w '%{http_code}\n' "http://127.0.0.1:8081/plays/seg214.ts?q=720p&expires=$E&token=wrong"
# Prints: 403 (the token is checked even though the file is cached)

X-Cache-Status is not a standard header. The config adds it from nginx's $upstream_cache_status variable, so you can see MISS, HIT, EXPIRED or STALE on every response.

What we measured

Pause and predict: 500 viewers each fetch segment 214 once, each with their own token. With the default key, how many requests reach the origin? Here is what the tests printed.

Measured on nginx 1.24.0 with a small Python test origin
TestSettingRequests that reached the origin
500 viewers, own token each, one segmentDefault key, the whole URL500, every request a miss
The same 500 viewersToken checked, key is host, path and q1, with 499 hits
200 misses at the same moment, origin takes 0.5 sproxy_cache_lock off200
The same 200 missesproxy_cache_lock on1, the other 199 waited and got a hit
max-age=1, s-maxage=60, asked again after 3 sDefault settings1, the second request was a hit
Vary: Accept-Language, Malayalam and EnglishDefault settings2, one copy per language
private, no-store, or a Set-Cookie headerDefault settingsEvery request, nothing was stored

The last row is worth remembering. The nginx docs say a response with a Set-Cookie header is not cached. On nginx defaults, a login cookie on a public page quietly drops that page's hit ratio to zero.

Request collapsing has a limit too. The nginx docs say that after proxy_cache_lock_timeout, 5 seconds by default, waiting requests go to the origin and their responses are not cached.

Where nginx and the standards disagree

RFC 5861 says stale-if-error covers anything that would produce a 500, 502, 503 or 504. The nginx docs say only that it applies in case of an error.

On nginx 1.24.0, the header alone served the stale copy when the origin was down and refused connections. When the origin answered with a 503, nginx passed the 503 to the viewer.

The source explains it. In ngx_http_upstream.c, the check for error statuses looks only at proxy_cache_use_stale, while connection failures also honour the header. The 1.28.0 source has the same checks.

Two more gaps showed up. RFC 9111 says a shared cache must not reuse a stale copy under s-maxage. With s-maxage=2, stale-while-revalidate=30 and background updates on, nginx served the stale copy anyway.

The stale-while-revalidate header also did not help on its own. With background updates off, the first request after expiry waited 1.00 s for a slow origin. With proxy_cache_background_update on, it got the old copy at once.

What can go wrong with a CDN?

Many CDN problems are caching mistakes, not CDN outages. A wrong cache key, a wrong TTL or a careless purge can send traffic home, show stale content, or worse, show one user's data to another.

  • Low hit ratio. Per-user tokens, tracking tags or random query parts split one file into many keys. Fix: check tokens at the edge and normalise the cache key.
  • Private data cached. A page like My Account gets cached and served to the next viewer. Fix: send Cache-Control: private or no-store on anything personal.
  • Stale content. A fixed file keeps its old name and a long TTL. Fix: fingerprinted names, or a purge for fixed URLs.
  • Purge floods. Purging everything makes every edge miss at once. Fix: purge by tag, use a soft purge, keep the shield and request collapsing on.
  • Cold start at a premiere. Every edge misses on the first segments together. Fix: warm the shield ahead of known crowds.
  • Live playlist cached too long. Viewers stall while new segments exist. Fix: keep the playlist TTL under the update interval.

The private data mistake is the most serious, because one stored copy is served to many people. The team's rule: nothing with a user's name, history or payment details is cached at the edge.

How do you explain a CDN in a system design interview?

Explain it as a shared cache placed close to users. Then spend your time on three decisions: what gets cached, under which key, and how it gets updated.

  1. Say what goes on the CDN

    Name the files many users share, like video segments, images and scripts. Say plainly what stays off it: personal pages and anything with private data.

  2. Draw the request path

    User, DNS mapping, edge, shield, origin. Explain a hit and a miss in one sentence each, and say that the shield should collapse matching misses into one origin request.

  3. Choose push, pull or both

    Pull for the long tail of files. Push or warm for crowds you can predict, like a launch or a premiere.

  4. Define the cache key

    Say which parts of the URL and headers belong in it, and which, like tokens and tracking tags, must be removed after the edge checks them.

  5. Set TTLs by file type

    Long TTLs for files that never change, short ones for lists and playlists. Mention s-maxage for CDN-only lifetimes and stale-if-error for pages that may be a little old.

  6. Plan updates and do the maths

    Fingerprinted names for files, tag purges for fixed URLs. Then work out origin traffic with and without a shield.

The traffic maths does not need to be exact. With 40,000 viewers at 3 megabits per second and a 96 percent hit ratio, the origin serves 4 percent of 120 gigabits per second. That is about 5 gigabits per second, and less with a shield in front.

Questions people ask

Is a CDN only useful for images and video?
No. A CDN helps with any file that many users share, such as scripts, style files, fonts, downloads and public pages. Many CDNs can also cache API responses that are the same for everyone, like a list of trending plays, for a few seconds.
What is a good CDN cache hit ratio?
It depends on the content. Popular files that never change, like video segments, can reach very high hit ratios. A site with many rarely used files will see lower numbers. Watch the trend more than the number: a sudden drop usually means the cache key or a header changed.
Should a small website use a push CDN or a pull CDN?
Most small sites should use a pull CDN. You point the CDN at your server once and it fetches files as people ask for them. Push makes sense when you know a big crowd is coming for specific files, or when you want no origin server at all.
How long does a CDN purge take?
It depends on the CDN, so never assume it is instant everywhere. A purge has to reach every edge server, and purge calls can fail or hit rate limits. Check your CDN's documentation, test it, and prefer new file names when you can, because those need no purge.
Can a CDN keep serving a site when the origin is down?
Partly, yes. Files that are still fresh in the cache keep working. With stale-if-error, a CDN may also serve stale copies for a set time. On nginx 1.24.0 that header covered a dead origin but not 500, 502, 503 or 504 replies. Logins and personal pages still fail.
What is an origin shield?
An origin shield is a middle cache between the edge servers and your origin. When an edge misses, it asks the shield instead of the origin. With request collapsing on, many edges asking for the same file at once cost the origin one request instead of hundreds.
Why does my CDN not cache some responses at all?
Usually a response header tells it not to. Under RFC 9111, private keeps a response out of shared caches like a CDN, and no-store keeps it out of every cache. nginx also skips responses that carry Set-Cookie or Vary: *. In our nginx 1.24.0 test, private, no-store and Set-Cookie each sent every request to the origin.

Sources and further reading

Practise this with Lala

Practise