Getting Started
Put Cloudflare in front of a site, set basic cache rules, and verify edge caching works
Getting Started
The fastest way to feel a CDN's value: front a real site with Cloudflare, configure caching, and verify with curl. Cloudflare's free tier covers the whole exercise; the patterns transfer to any other CDN.
Sign Up & Add a Site
- Sign up at cloudflare.com (free tier is enough).
- Add a Site → enter your domain.
- Cloudflare scans existing DNS records and shows them. Confirm.
- Change nameservers at your registrar to the two Cloudflare-provided ones. DNS propagation takes minutes to hours.
- Once active, Cloudflare proxies traffic for orange-cloud (
🟠) DNS records.
For a quick test without changing real DNS, point a subdomain (cdn-test.example.com) at Cloudflare and play there.
Default Behavior
Out of the box, Cloudflare:
- Caches static file extensions (CSS, JS, images, fonts, PDFs).
- Doesn't cache HTML by default — most CMS responses look dynamic.
- Terminates TLS at the edge with a free cert.
- Compresses with Brotli / gzip when supported.
- Enables HTTP/2 and HTTP/3.
You see the result in the cf-cache-status header:
curl -I https://example.com/style.css
# HTTP/2 200
# cf-cache-status: HIT ← served from cache
# age: 3421
# cache-control: public, max-age=14400| Status | Meaning |
|---|---|
HIT | Served from the edge cache |
MISS | Not in cache; fetched from origin and now cached |
EXPIRED | Was cached but TTL elapsed; re-fetched |
REVALIDATED | TTL elapsed but origin returned 304 |
BYPASS | A rule said don't cache |
DYNAMIC | Not eligible (cookies, method, content type) |
STALE | Cache TTL expired, served stale (origin down or stale-while-revalidate) |
Tell the CDN What to Cache
Origins control caching via response headers:
Cache-Control: public, max-age=31536000, immutable
Cache-Control: public, max-age=300, s-maxage=3600, stale-while-revalidate=60
Cache-Control: private, no-storeKey directives:
| Directive | Meaning |
|---|---|
public | OK to cache anywhere (including CDN) |
private | Only the user's browser may cache |
no-store | Don't cache anywhere |
max-age=N | Browser cache TTL in seconds |
s-maxage=N | CDN/shared cache TTL (overrides max-age for CDN) |
immutable | Browser won't revalidate within max-age |
stale-while-revalidate=N | After TTL, serve stale for N seconds while refreshing in background |
stale-if-error=N | Serve stale for N seconds if origin errors |
Recommended Recipes
# Versioned static assets (hash in filename): cache forever
Cache-Control: public, max-age=31536000, immutable
# Public HTML page: short edge TTL with refresh
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=60
# Per-user response: never share
Cache-Control: private, no-store
# API response that's the same for all users for 1 min
Cache-Control: public, s-maxage=60Override at the CDN
Cloudflare lets you override caching with Page Rules / Cache Rules even if origin headers say otherwise:
URL pattern: example.com/blog/*
Cache Level: Cache Everything
Edge Cache TTL: 1 hour
Browser Cache TTL: 5 minutesThis is essential when the origin sends conservative headers but you want aggressive edge caching.
Purge When Content Changes
# Purge one URL
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data '{"files":["https://example.com/blog/my-post"]}'
# Purge by tag (requires setting Cache-Tag header on origin responses)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "Authorization: Bearer $TOKEN" \
--data '{"tags":["post-42"]}'
# Purge everything (nuclear option)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "Authorization: Bearer $TOKEN" \
--data '{"purge_everything":true}'Wire purges into your deploy / CMS:
- CMS publish → purge the page URLs.
- Image processing finished → purge
/images/{id}/*. - Deploy ships new bundle → purge
/assets/*(or trust the hash-in-filename strategy and don't purge).
Verify End-to-End
# First request → MISS
curl -I https://example.com/blog/hello
# Repeated request → HIT
curl -I https://example.com/blog/hello
# After purge → MISS again
curl -X POST .../purge_cache --data '{"files":[...]}'
curl -I https://example.com/blog/helloIf you see cf-cache-status: DYNAMIC when you expected HIT, the most common causes:
- Cookies in the request (Cloudflare bypasses cache when
Cookieis set unless you tell it not to). set-cookiein the response (same).Vary: *or unhelpfulVaryvalues.Cache-Control: privateorno-storeon the response.- POST/PUT method (only
GET/HEADcached).
Inspect with curl -v to see exactly what's coming back.
Image Optimization
Most CDNs offer on-the-fly image transformations:
# Cloudflare Images / Polish
https://example.com/cdn-cgi/image/width=400,format=auto,quality=85/photo.jpg
# Fastly Image Optimizer
https://example.com/photo.jpg?width=400&auto=webp
# imgix (dedicated)
https://example.imgix.net/photo.jpg?w=400&auto=format,compressThe same source file gets resized, recompressed, and converted to webp/avif per request, then cached at the edge. Don't ship one giant JPG to every device; let the CDN serve the right size.
What's Next
You can put a CDN in front of a site and verify caching. Next:
- Caching Strategies — surrogate keys, stale-while-revalidate, Vary, image optimization in depth
- Best Practices — multi-CDN, security, observability, cost