$ cat /blog/cloudflare-caching-tips.md

Cloudflare Caching Tips: 7 Production-Ready Tweaks I Use to Cut Latency

Hands-on Cloudflare caching tricks—edge TTLs, cache keys, and page rules—that actually ship faster responses in production.

Cloudflare Caching Tips: 7 Production-Ready Tweaks I Use to Cut Latency

I’ve been tuning CDNs for real products in production for more than a decade. Cloudflare is a practical tool if you respect the rules of caching and avoid turning your site into a radioactive cache. Below are 7 tweaks I actually use in bootstrapped products to shave tens to hundreds of milliseconds off response times without complicating deployment or inflating costs.

If you’re a solo founder or small team like me, these changes scale with your product and don’t require a Herculean refactor. They’re honest, production-tested, and focused on tangible wins.


1) Separate asset caching from HTML: give assets a long, boring TTL

The quickest wins come from caching static assets that never change (or change rarely) and shipping those with minimal overhead.

  • Assets pattern: your CSS, JS, fonts, and images (e.g., /static/, /assets/).
  • Edge Cache TTL: 1 year for truly static assets.
  • Browser Cache TTL: 1 year so browsers reuse them aggressively.
  • Cache Level: Cache Everything (for the asset patterns) to ensure Cloudflare serves them from edge even if they’re not “static” by default.

Why this helps: a typical site spends a lot of time re-fetching JS/CSS on every page load. If those files are cached at the edge and by the browser, you drop latency dramatically on every request, especially for returning visitors.

Code-free rules are fine, but I also like a small API-driven approach to manage rules in a repeatable way if you’re deploying often.

Example (conceptual rule set):

  • URL pattern: example.com/assets/* and example.com/static/*
  • Cache Level: Cache Everything
  • Edge Cache TTL: 31536000 (1 year)
  • Browser Cache TTL: 31536000

Actionable tip: make sure these assets have far-future versioning (e.g., hash in filenames) so you can safely push updates without forcing everyone to clear caches.

Code snippet (curl for a rule you could adapt via API):

curl -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/pagerules" \
  -H "X-Auth-Email: <EMAIL>" \
  -H "X-Auth-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  --data '{
    "targets":[{"target":"url","constraint":{"operator":"matches","value":"https://example.com/assets/*"}}],
    "actions":[{"id":"cache_level","value":"cache_everything"},{"id":"edge_cache_ttl","value":"31536000"},{"id":"browser_cache_ttl","value":"31536000"}],
    "priority":1,
    "status":"active"
  }'

Tip from the trenches: test with a real user in a low-latency region (West Coast, for me) and confirm HITs in CF-Cache-Status for assets after a reload.


2) Cache HTML with caution: Cache Everything, but bypass personal data

HTML is where you have to be careful. You don’t want a homepage or a product listing caching user-specific data, cart contents, or login state.

  • Rule for generic HTML pages: Cache Everything with a modest Edge Cache TTL (2–6 hours is a safe default for most apps that don’t update every second).
  • Bypass on cookies: Cloudflare can bypass the cache when a cookie is present (e.g., session_id, cart_id, or authentication cookies). This is crucial for preventing stale, personalized content from being served.

What to watch:

  • If you run user dashboards or personalized pages, ensure the URL patterns that serve those pages are excluded from this caching rule, or rely on a Worker to handle dynamic rendering for those paths.

Example rule:

  • URL pattern: example.com/* (but not /auth/* or /dashboard/*)
  • Cache Level: Cache Everything
  • Edge Cache TTL: 7200 (2 hours)
  • Browser Cache TTL: 1 hour
  • Bypass Cache on Cookie: session_id, user_id

Notes:

  • For highly dynamic pages, you may want to lower Edge TTL or skip the HTML caching entirely and rely on JSON APIs and client-side rendering with static shell.

Code snippet: how I validate that HTML is serving cleanly from edge

# Quick sanity check
curl -I https://example.com/  # Look for CF-Cache-Status: HIT
curl -I https://example.com/dashboard  # Expect MISS or BYPASS if cookies present

Takeaway: HTML caching can shave a meaningful chunk off latency, but you must be disciplined about what you cache and when you bypass.


3) Make your cache keys predictable and safe: exclude cookies, include only what matters

Cache keys are the guardrails of how ambiguous content gets cached. If your key includes user-specific data (like a session cookie or user_id), you’ll poison the cache with personalization and cause incorrect data to be served to other users.

Rule of thumb:

  • Exclude cookies by default from the cache key for pages that are not personalized.
  • If you must support localized content, consider including a language or region query parameter in the cache key rather than cookies.

In Cloudflare terms, you can adjust the cache key to include the critical query parameters (e.g., lang, region, version) and exclude cookies. This lets you keep a single cached asset across users that share the same presentation.

Example (conceptual):

  • Cache Key includes: scheme, host, path, query string parameters lang and region
  • Excludes: all cookies (e.g., Cookie: session_id, auth_token)

Practical check:

  • Use a URL like https://example.com/blog?lang=en&page=1 and ensure that https://example.com/blog?lang=es&page=1 is cached separately only if you truly need both.

Test approach:

# Force a request with and without the cookie to compare
curl -I "https://example.com/blog?lang=en"  # CF-Cache-Status: HIT
curl -I "https://example.com/blog?lang=en" -H "Cookie: session_id=abc123"  # Expect BYPASS or MISS

Why it matters: a clean, cookie-agnostic cache key dramatically increases cache hit rates for non-personalized content, which is where most latency benefits come from.


4) Layer in Cloudflare Workers for smart, edge-driven caching

If you have even modest dynamic needs, a small Cloudflare Worker can give you precise control without risking your origin or complicating the app.

What a Worker buys you:

  • Smart revalidation: serve cached content and revalidate in the background.
  • Fine-grained rules for specific routes (e.g., blog posts vs. product pages).
  • Lightweight transformations (minify HTML on the fly, strip heavy scripts, etc.), without touching your origin.

A minimal Worker pattern:

  • Cache-first retrieval from the edge
  • If not in cache, fetch from origin and populate the cache
  • Optional background revalidation

Example (JavaScript, Cloudflare Worker):

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const cache = caches.default
  try {
    // Try to serve from cache
    const cached = await cache.match(request)
    if (cached) {
      // Optional: kick off a lightweight revalidation in the background
      revalidate(request)
      return cached
    }

    // Not cached yet; fetch from origin
    const res = await fetch(request)
    if (request.method === 'GET' && res.ok) {
      event.waitUntil(cache.put(request, res.clone()))
    }
    return res
  } catch (e) {
    // Fallback: return a safe cached copy if available
    const cachedFallback = await cache.match(request)
    return cachedFallback || new Response('Service Unavailable', { status: 503 })
  }
}

async function revalidate(request) {
  try {
    const res = await fetch(request)
    if (request.method === 'GET' && res.ok) {
      const cache = caches.default
      await cache.put(request, res.clone())
    }
  } catch (e) {
    // Swallow; background revalidation shouldn't block response
  }
}

Practical notes:

  • Keep the Worker lean. It should be a small layer that complements your existing rules, not a full-blown replacement for your origin logic.
  • Use the Cache API for deterministic behavior and to avoid pulling the origin unnecessarily.

This approach is especially valuable for blogs or product pages where you want near-instant delivery while still allowing fresh content to surface quickly after updates.


5) Automate purges on deploy and sane cache-busting

Every time you deploy, you should purge the Cloudflare cache where you know assets have changed. A manual purge is slow and error-prone; automation keeps you sane and ensures users aren’t staring at stale content.

Recommended pattern:

  • Purge specific files that change on deploy (e.g., updated CSS/JS, new images, blog posts).
  • If you do a full site deploy and content-wide change, purge everything.

Automation example (Cloudflare API, curl):

# Purge specific files
curl -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/purge_cache" \
  -H "X-Auth-Email: <EMAIL>" \
  -H "X-Auth-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  --data '{"files":["https://example.com/assets/app.js","https://example.com/assets/styles.css","https://example.com/blog/post-2025-01.html"]}'

# Purge everything (less common, but sometimes needed after a major rewrite)
curl -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/purge_cache" \
  -H "X-Auth-Email: <EMAIL>" \
  -H "X-Auth-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  --data '{"purge_everything":true}'

Operational tip:

  • Integrate purge calls into your CI/CD pipeline—right after a successful deployment, purge specific files. This keeps latency down without waiting for TTLs to expire.

6) Enable Origin Shield and use tiered caching to reduce origin load

Origin Shield is Cloudflare’s extra caching layer near your origin. It helps reduce the number of requests hitting your origin during traffic bursts and global cache misses. If your origin is your bottleneck (database, API, heavy SSR), enabling Origin Shield can noticeably reduce latency.

What I see in the real world:

  • During traffic spikes, pure edge caching with reasonable TTLs still flushes a lot of requests to origin. Origin Shield acts like a mini-co-located cache near the origin.

Practical setup:

  • Turn on Origin Shield for the region closest to most of your users (e.g., Pacific Northwest for West Coast latency).
  • Pair with a sensible Edge Cache TTL on HTML and assets so that you get a balance between freshness and origin load.

Observability tip: monitor CF-Cache-Status distribution and origin fetch counts to verify Origin Shield is actually reducing origin hits.

Takeaway: Origin Shield is a simple lever that can dramatically reduce pressure on your origin during peak times without changing your app logic.


7) Observe, measure, and iterate: test with real users and reliable signals

Caching is not a “set it and forget it” feature. You need feedback from real user traffic to know what’s actually working.

What I track:

  • CF-Cache-Status for key routes (HIT, MISS, EXPIRED, BYPASS)
  • Latency distribution (P95, P99) before and after changes
  • Origin fetch count and origin latency
  • Visual regressions: ensure cached pages render correctly after cache hits

How I test:

  • Use curl -I or simple fetches to confirm Cache Status headers.
  • Run synthetic loads through a staging domain configured the same way as production.
  • Compare latency and error rates between scenarios with and without caching.

Example quick check commands:

# Basic cache hit check
curl -sI https://example.com/blog/2024/post-good-ideas
# Expect CF-Cache-Status: HIT on subsequent runs
curl -sI https://example.com/blog/2024/post-good-ideas

# Check for endpoints that should bypass due to login or personalization
curl -sI -H "Cookie: session_id=abc123" https://example.com/account

If you see too many BYPASS or MISS responses on pages that should be cacheable, revisit your cache-key strategy and the cookie-bypass rules. Small changes here yield big latency benefits.


Conclusion / Takeaways

  • Start with assets: long edge and browser TTLs for static assets. This alone often yields the largest, most repeatable latency wins.
  • Cache HTML cautiously: use Cache Everything for non-personal pages with a conservative TTL and bypass on cookies for anything user-specific.
  • Get your cache keys right: exclude cookies by default, include only necessary query parameters, and keep personalization separate from globally cached content.
  • Leverage Workers for precise control: a lightweight edge layer can deliver freshness with minimal origin impact.
  • Automate purges: tie cache invalidation to your deploy pipeline so users see updates immediately.
  • Enable Origin Shield where it makes sense: reduce origin load and improve performance during spikes.
  • Measure and iterate: use CF-Cache-Status and latency metrics to validate improvements and steer ongoing tweaks.

If you want a quick-start checklist I actually use for new projects, I’ve kept a practical one:

  • [ ] Create assets/* and fonts/* rules with Edge TTL 1 year, Browser TTL 1 year.
  • [ ] Create a cautious HTML rule for /* with Edge TTL 2–6 hours; bypass on session cookies.
  • [ ] Audit all pages for personalization; adjust cache keys to avoid cookies in the key.
  • [ ] Deploy a small Cloudflare Worker to handle cache-first requests and optional revalidation.
  • [ ] Add CI/CD steps to purge specific files after deploy (or purge everything in a controlled scenario).
  • [ ] Enable Origin Shield for the region with the highest traffic share.
  • [ ] Set up a monitoring dashboard to track CF-Cache-Status and origin fetches weekly.

If you’re curious about the exact approach I’m using for a specific project, ping me on X (@fullybootstrap). I’m happy to share domain-specific patterns, like how I handle a mixed blog/e-commerce setup behind Cloudflare, or how I tune caching for a small SaaS landing page plus API.

Happy caching. May your latency be low, your cache hits high, and your deploys boringly smooth.