Your API is getting hammered. Same endpoints, same responses, thousands of requests. Your origin server is sweating. Here's how I used Cloudflare Cache Rules to serve API responses from the edge—no code changes required.
The Problem
I run a URL shortener. The redirect API (/api/v1/public/a/{shortUrl}) returns JSON with the destination URL, and the frontend handles the redirect. Popular links get hit thousands of times a day.
The thing is—the response rarely changes. A short URL pointing to https://example.com will point there for days, weeks, months. Why hit the origin every single time?
Why Not Just Use Cache-Control Headers?
You could add Cache-Control: public, max-age=3600 in your backend code. But:
- Code changes = deployments, testing, potential bugs
- Cloudflare doesn't cache API responses by default (even with headers)
- Less flexibility—TTLs are baked into code
Cache Rules let you handle this entirely at the infrastructure layer.
The Setup
Cloudflare Dashboard → Caching → Cache Rules → Create Rule
The Expression
(http.request.uri.path contains "/api/v1/public/a/" and not any(http.request.headers["jo4_url_pwd"][*] ne ""))
This matches:
- Requests to the short URL API
- AND excludes requests with a password header (for protected URLs)
Why exclude password-protected URLs? If you cache them, anyone hitting that URL gets the cached response—including the destination. Security hole.
The Cache Settings
| Setting | Value | Why |
|---|---|---|
| Cache eligibility | Eligible for cache | Enable caching for matching requests |
| Edge TTL | 2 hours | How long Cloudflare edge holds it |
| Browser TTL | 1 hour | How long the client browser holds it |
Why Staggered TTLs?
The 1hr browser + 2hr edge combo is intentional:
- 0-1hr: Browser serves from local cache (instant, zero network)
- 1-2hr: Browser cache expired, but Cloudflare edge still has it (fast, no origin)
- 2hr+: Both expired, request hits origin (fresh data)
This maximizes cache hits while keeping staleness reasonable.
What About Analytics?
Here's the tradeoff: if Cloudflare serves a cached response, your origin never sees the request. No analytics recorded.
For my use case, this is fine. I care about unique visitors, not repeat hits from the same client. The first request hits origin (analytics recorded), subsequent requests get cached.
If you need accurate hit counts:
- Use a shorter TTL (15-30 minutes)
- Implement client-side analytics beacons
- Or just don't cache—sometimes origin load is acceptable
The Visual Builder
If you prefer clicking over typing expressions:
When incoming requests match...
- Field:
URI Path| Operator:contains| Value:/api/v1/public/a/ - And
- Field:
Request Header| Name:jo4_url_pwd| Operator:equals| Value: (empty)
Actually, for "header not present", the expression editor is easier. The visual builder's equals empty works but the expression not any(http.request.headers["header"][*] ne "") is cleaner.
Cache Invalidation
What if a URL changes? Two options:
- Wait for TTL (2 hours max in my case—acceptable)
- 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://yourdomain.com/api/v1/public/a/abc123"]}'
For most use cases, just pick a TTL you can live with and skip the invalidation complexity.
Bonus: Serve Stale While Revalidating
Enable "Serve stale content while revalidating" in your cache rule. When cache expires:
- Cloudflare immediately serves the stale cached response
- Simultaneously fetches fresh content from origin
- Next request gets the fresh version
Users never wait for origin. Cache stays warm.
Results
Before: Every request hit origin. Popular URLs caused load spikes.
After: ~90% of repeat requests served from edge. Origin load dropped significantly. Response times for cached requests: <50ms globally (served from nearest Cloudflare POP).
Lessons Learned
- Cloudflare doesn't cache API responses by default—you need Cache Rules to enable it
- Exclude sensitive requests—use expressions to skip password-protected or authenticated endpoints
- Stagger your TTLs—browser TTL < edge TTL gives you layered caching
- Accept the analytics tradeoff—or implement client-side tracking
- Skip invalidation if you can—pick a TTL you can live with, it's simpler
- Zero code changes—this is purely infrastructure config
Have questions about edge caching? Drop a comment below.
Building jo4.io - URL shortener with analytics, bio pages, and team workspaces.
Top comments (0)