CDN and Cache Fundamentals
By the end of this lesson you will understand what Cloudflare caches by default, how to control caching with headers and rules, and how to purge cached content when needed.
What Is a CDN?
A Content Delivery Network (CDN) is a distributed network of servers that stores copies of your content at locations closest to your users. Instead of every request traveling to your origin server (which might be in one data center), requests are served from the nearest edge node (Point of Presence / PoP).
Before CDN vs After CDN
| Without CDN | With Cloudflare CDN | |
|---|---|---|
| Latency | Every request goes to origin (200–500ms) | Served from nearest PoP (~20ms) |
| Origin load | All traffic hits your server | Only cache misses reach origin |
| Bandwidth costs | You pay for all egress | Cloudflare absorbs cached traffic |
| Availability | Single point of failure | Distributed across 330+ locations |
| Cost | Egress fees from cloud providers | Free (Cloudflare doesn't charge for bandwidth) |
What Cloudflare Caches by Default
Cloudflare caches static files based on file extension. If a request URL ends with one of these extensions, Cloudflare will cache it automatically:
| Category | Extensions |
|---|---|
| Images | .jpg, .jpeg, .png, .gif, .webp, .svg, .ico, .bmp, .tif, .tiff |
| Scripts/Styles | .js, .css |
| Fonts | .woff, .woff2, .ttf, .eot, .otf |
| Documents | .pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx |
| Media | .mp3, .mp4, .ogg, .wav, .avi, .mov, .webm |
| Web | .html, .htm (only with specific cache headers) |
| Other | .swf, .zip, .gz, .tar |
By default, Cloudflare does not cache HTML pages. HTML is considered dynamic content and is always fetched from the origin. You can override this behavior with Cache Rules.
Cache Status Headers
Every response from Cloudflare includes a cf-cache-status header that tells you whether the content was served from cache:
curl -sI https://example.com/style.css | grep cf-cache-status
# cf-cache-status: HIT
| Status | Meaning |
|---|---|
| HIT | Content served from Cloudflare's cache (fastest) |
| MISS | Content not in cache — fetched from origin, now cached for next request |
| EXPIRED | Content was in cache but expired — re-validated with origin |
| BYPASS | Cloudflare was instructed to skip cache (e.g., by a cache rule or no-cache header) |
| DYNAMIC | Cloudflare treats this as uncacheable content (default for HTML) |
| REVALIDATED | Cache was stale, origin confirmed content hasn't changed (304 response) |
Cache-Control Headers
You control caching behavior from your origin server using the Cache-Control HTTP header:
Cache-Control: public, max-age=3600, s-maxage=86400
| Directive | Meaning |
|---|---|
public | Any cache (including CDN) can store this response |
private | Only the browser can cache this (CDN must not cache) |
max-age=N | Browser caches for N seconds |
s-maxage=N | CDN/shared cache caches for N seconds (overrides max-age for CDN) |
no-cache | Cache must revalidate with origin before serving |
no-store | Do not cache at all — anywhere |
must-revalidate | Once expired, must check origin before serving stale content |
Recommended Cache-Control Strategies
| Content Type | Header | Reasoning |
|---|---|---|
| Static assets (JS, CSS, images) | public, max-age=31536000, immutable | Versioned files (with hash in filename) — cache forever |
| HTML pages | public, max-age=0, must-revalidate | Always check for fresh content |
| API responses | private, no-cache | User-specific data, no CDN caching |
| Fonts | public, max-age=31536000 | Fonts rarely change |
Use content hashing in your asset filenames (e.g., app.a1b2c3.js) and set max-age=31536000 (1 year). When you update the file, the hash changes, so the browser fetches the new version. This gives you both maximum caching and instant updates.
Cache Rules
Cache Rules let you customize caching behavior beyond the defaults — directly in the Cloudflare dashboard, without modifying your origin server.
Common Cache Rules
Cache Everything (Including HTML)
By default, Cloudflare doesn't cache HTML. To cache entire pages:
| Setting | Value |
|---|---|
| When | URI Path contains /blog/ |
| Then | Cache eligibility: Eligible for cache |
| Edge TTL | 1 hour |
| Browser TTL | 5 minutes |
Bypass Cache for Admin Pages
| Setting | Value |
|---|---|
| When | URI Path starts with /admin or /wp-admin |
| Then | Cache eligibility: Bypass cache |
Long Cache for Static Assets
| Setting | Value |
|---|---|
| When | URI Path starts with /assets/ or /static/ |
| Then | Edge TTL: 1 month |
| Browser TTL | 1 year |
Free Plan Limits
| Feature | Free Plan |
|---|---|
| Cache Rules | 10 rules |
| Edge TTL control | ✅ |
| Browser TTL control | ✅ |
| Bypass cache | ✅ |
| Cache Everything | ✅ |
Purging Cache
When you update content on your origin, the CDN might still serve the old cached version. Purging clears the cache so Cloudflare fetches fresh content.
Purge Methods
| Method | Use Case | How |
|---|---|---|
| Purge Everything | Major site update | Dashboard → Caching → Purge Everything |
| Purge by URL | Updated a specific page or asset | Dashboard → Caching → Custom Purge → Enter URL(s) |
| Purge by Tag | Purge all assets matching a cache tag (Enterprise) | API with Cache-Tag header |
| Purge by Prefix | Purge all URLs under a path (Enterprise) | API with URL prefix |
Purge via API
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
--data '{
"files": [
"https://example.com/style.css",
"https://example.com/app.js"
]
}'
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
--data '{"purge_everything": true}'
Purge Everything clears all cached content across all edge nodes. The next request for every resource will be a cache miss, which can spike traffic to your origin. Use targeted purges whenever possible.
Edge TTL vs Browser TTL
Understanding the difference between these two TTLs is critical:
| Edge TTL | Browser TTL | |
|---|---|---|
| Where | Cloudflare's edge servers | Visitor's browser |
| Controls | How long Cloudflare caches the content | How long the browser caches the content |
| Purge possible? | ✅ Yes (via dashboard or API) | ❌ No — you can't purge a visitor's browser cache |
| Header | Controlled by s-maxage or Cache Rules | Controlled by max-age |
Common Misconceptions
"Cloudflare caches everything including HTML by default"
Reality: By default, Cloudflare only caches static assets (images, JS, CSS, fonts). HTML is treated as "DYNAMIC" and always fetched from origin. Use Cache Rules to cache HTML.
"Purging cache is instant worldwide"
Reality: Purging propagates across Cloudflare's 330+ PoPs within ~30 seconds in most cases, but it's not truly instantaneous.
"More caching is always better"
Reality: Caching user-specific content (dashboards, authenticated pages) creates security risks where one user could see another user's data. Only cache truly public, static content.
Anti-Patterns to Avoid
| Don't Do This | Do This Instead |
|---|---|
| Cache authenticated/personalized pages | Use private, no-cache for user-specific content |
| Set very short Edge TTLs (< 1 minute) | Use reasonable TTLs (1 hour+) and purge when content changes |
| Purge Everything after every deploy | Purge specific URLs or use cache-busting filenames |
Ignore cf-cache-status header | Monitor cache HIT ratios to optimize performance |
| Never set Cache-Control headers on origin | Explicitly set headers to control behavior |
Key Takeaways
- Cloudflare CDN caches static assets by default — images, JS, CSS, fonts — for free.
- HTML is not cached by default. Use Cache Rules to cache static HTML pages.
- The
cf-cache-statusheader tells you whether content was served from cache. - Use Cache-Control headers on your origin to control caching behavior.
- Cache Rules (10 free) let you customize caching without changing your origin server.
- Purge responsibly — prefer targeted URL purges over purging everything.
What's Next
- Continue to Speed Brain to learn about Cloudflare's prefetch acceleration.