Configuring Origin Shield on Cloudflare and Fastly
Without a shield tier, every edge point of presence (PoP) that misses on a cacheable object fetches it from origin independently. A single popular asset served from fifty PoPs worldwide can generate fifty separate origin requests for the same bytes, defeating the purpose of origin shielding and request collapsing. This guide walks through the concrete dashboard and API steps to enable a shield tier on Cloudflare and Fastly, then verify and measure the result.
Prerequisite Concepts
Before changing shield configuration, make sure the underlying mechanisms are clear:
- Origin Shielding and Request Collapsing — the general problem of thundering-herd origin load and how a shield tier plus request collapsing solves it together.
- How CDN Cache Keys Are Generated — shielding only helps if edge PoPs agree on the same cache key for a given request; key fragmentation defeats shielding just as badly as no shielding at all.
- Mastering
max-ageands-maxagedirectives — a response must be cacheable by the shared cache tier before shielding has anything to collapse.
Step-by-Step Resolution
Step 1 — Enable Cloudflare Tiered Cache
Cloudflare’s shield mechanism is called Tiered Cache (the successor to the older “Argo Tiered Cache” product name — Tiered Cache is now a free zone-level feature, with Argo Smart Routing as a separate paid add-on for the origin-to-edge leg). In the dashboard: Caching → Tiered Cache, then choose a topology:
- Smart Tiered Cache — Cloudflare automatically selects the upper-tier data center with the lowest latency to your origin. This is the correct default for most zones.
- Custom Tiered Cache Topology — you explicitly assign a specific upper-tier data center, useful when your origin is in a region Cloudflare’s automatic selection doesn’t optimize for, or when you have data-residency constraints.
To enable it via the API instead of the dashboard:
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/argo/tiered_caching" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"value": "on"}'
Expected response:
HTTP/2 200
Content-Type: application/json
{"success":true,"errors":[],"messages":[],"result":{"id":"tiered_caching","value":"on","editable":true}}
Once enabled, every edge PoP that misses on a cacheable object routes the miss through its assigned upper-tier PoP instead of dialing origin directly. If the upper tier already holds the object, it serves the edge PoP without any origin traffic at all.
Step 2 — Designate a shield POP in Fastly
Fastly calls the same concept shielding, and it is configured per-backend rather than per-zone. In the Fastly UI: Origins → (your backend) → Edit, then set the Shielding dropdown to the POP nearest your origin’s physical location.
Via the API, set the shield property when creating or updating a backend:
curl -X PUT "https://api.fastly.com/service/${SERVICE_ID}/version/${VERSION}/backend/origin_backend" \
-H "Fastly-Key: ${FASTLY_API_TOKEN}" \
-H "Accept: application/json" \
--data-urlencode "shield=dca-dc17" \
--data-urlencode "address=origin.example.com"
dca-dc17 is a Fastly POP code (Washington DC). Pick the code closest to your origin’s actual network location — Fastly publishes the current POP list and codes in its documentation. Activate the version afterward for the change to take effect:
curl -X PUT "https://api.fastly.com/service/${SERVICE_ID}/version/${VERSION}/activate" \
-H "Fastly-Key: ${FASTLY_API_TOKEN}"
With shielding set, every non-shield POP that misses forwards the request to the shield POP first. Only the shield POP is permitted to reach origin directly.
Step 3 — Verify with curl
On Cloudflare, request the asset from cold and inspect CF-Cache-Status and Age:
curl -sI https://example.com/asset.js \
| grep -iE '(cf-cache-status|cf-ray|age|cache-control)'
CF-Cache-Status: MISS
CF-RAY: 7c1e2f9a1b2c3d4e-IAD
Age: 0
Cache-Control: public, max-age=3600, s-maxage=86400
Repeat the request against a different edge PoP (use --resolve or a different Accept-Language/geographic vantage point if you have one) and look for CF-Cache-Status: HIT with a non-zero Age — confirming the second PoP received the object from the shield tier rather than triggering a second origin fetch.
On Fastly, the equivalent check uses X-Served-By and Age:
curl -sI https://example.com/asset.js \
| grep -iE '(x-served-by|x-cache|age|cache-control)'
X-Served-By: cache-dca-dc17-DCA, cache-lhr-EGLL
X-Cache: HIT, MISS
Age: 12
X-Served-By lists every POP the request traversed, shield first. A pattern of MISS at the shield followed by HIT at subsequent edge POPs on later requests confirms the shield is absorbing repeat misses.
Step 4 — Measure origin offload
The real payoff of shielding is quantifiable at the origin. Compare origin request counts before and after enabling shielding using each vendor’s analytics API.
Cloudflare GraphQL Analytics (origin response counts by zone, grouped by hour):
curl -X POST "https://api.cloudflare.com/client/v4/graphql" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"query":"query { viewer { zones(filter: {zoneTag: \"'"${ZONE_ID}"'\"}) { httpRequestsAdaptiveGroups(limit: 24, filter: {datetime_geq: \"2026-07-05T00:00:00Z\"}) { sum { edgeResponseBytes } dimensions { datetimeHour } } } } }"}'
Fastly real-time stats (origin fetches per second):
curl -s "https://rt.fastly.com/v1/channel/${SERVICE_ID}/ts/0" \
-H "Fastly-Key: ${FASTLY_API_TOKEN}" \
| jq '.Data[].aggregated.origin_fetches'
A working shield configuration shows origin request volume drop sharply relative to total edge requests — the ratio of edge requests to origin fetches is the offload factor. If that ratio stays close to 1:1 after enabling shielding, the response is likely not cacheable (check for private, no-store, or a missing freshness directive) rather than a shielding misconfiguration.
Expected Output / Verification
- Cloudflare: the shield PoP shows
CF-Cache-Status: MISSon the first request for an object; all other edge PoPs subsequently showHITwithout their own origin fetch. - Fastly:
X-Served-Bylists the shield POP first, andX-CacheshowsMISSthere butHITat edge POPs on later requests for the same object. Ageincreases monotonically across repeated requests to the same edge PoP, confirming the object is being served from cache rather than re-fetched.- Origin access logs (or the CDN’s analytics API) show a request-to-origin-fetch ratio well above 1:1, proportional to the number of active edge PoPs.
Edge Cases
- Shield POP chosen far from origin — if the designated shield POP has high latency to origin, every miss (rare after warm-up, but still on the critical path for cold objects) suffers extra round-trip time. Pick the POP with the lowest latency to your origin’s actual network path, not just geographic proximity.
Cache-Control: privatebypasses the shield entirely — RFC 9111 requires shared caches, including shield tiers, to refuse to store private responses. A private response passes straight through to origin from every edge PoP, exactly as if shielding were disabled.- Purge scope must include the shield tier — a purge that only targets edge PoPs and not the shield tier leaves a stale copy at the shield, which then repopulates edge PoPs with stale data on their next miss. Confirm your purge API call invalidates all tiers.
- Tiered Cache and Argo Smart Routing are separate features — Tiered Cache handles edge-to-shield-to-origin caching topology; Argo Smart Routing optimizes the network path for cache misses and dynamic content. Enabling one does not enable the other.
Frequently Asked Questions
Does Tiered Cache replace the need for s-maxage?
No. Tiered Cache changes the topology edge nodes use to reach origin on a miss; it does not change the freshness lifetime calculation. The response still needs s-maxage or Cache-Control directives that make it cacheable in the first place — shielding only matters once the response is already storable.
Why is CF-Cache-Status showing MISS at every edge PoP even with Tiered Cache enabled?
Check that the response is actually cacheable — private, no-store, or a Set-Cookie response without explicit caching directives bypasses Tiered Cache entirely. Also confirm the zone’s Tiered Cache topology isn’t set to off, and that no Cache Rule is forcing a bypass.
Can I pin a different shield POP than the one Cloudflare or Fastly picks automatically?
Yes. Fastly lets you explicitly assign a shield POP per backend. Cloudflare’s Smart Tiered Cache selects the upper-tier data center automatically based on latency to origin, but Custom Tiered Cache Topology lets you pin it explicitly when you need control over data residency or peering paths.
How much origin offload should I expect?
It depends on how many edge PoPs serve your traffic and how concentrated your cache keys are. Sites with global traffic and widely dispersed URLs commonly see origin request volume drop by 60-90% because dozens of edge PoPs collapse into a single shield-to-origin path per cached object.
Related
- How to Debug CDN Cache Key Mismatches — if shielding seems ineffective, mismatched cache keys across edge PoPs are a common root cause.
- Request Collapsing in Varnish with VCL — the equivalent single-node collapsing mechanism when you run Varnish in front of origin instead of a managed CDN shield.
- Purging Cloudflare Cache Tags with the API — ensures purges invalidate both the shield tier and edge PoPs together.
- Using
Vary: Accept-EncodingWithout Fragmenting Cache — a common cause of the cache-key fragmentation that undermines shield effectiveness.