Enabling Tiered Cache and Origin Shield on Cloudflare
After working through this guide you will be able to check which tiering entitlements your zone actually has, pick between generic, smart, and regional topologies, turn the upper tier on through the dashboard, the API, or Terraform, prove from several regions that edge misses are being filled by that tier instead of by your origin, and diagnose the five ways the change silently does nothing.
Cloudflare’s version of an origin shield is called Tiered Cache, and unlike CloudFront it is a zone-level switch rather than a region you name. That difference shapes the whole procedure: you do not choose a shield location directly, you choose a topology and let the network pick the upper tier — so verification is about confirming which colo your origin sees traffic from, not about reading a region string back out of a config file. The mechanism itself, and how it compares with CloudFront and Fastly, is covered in the parent guide on tiered caching and origin shield.
Key implementation objectives:
- Confirm the zone is proxied and that Smart Tiered Cache Topology is actually available on the plan before writing any config.
- Enable the correct topology through the API or Terraform so the setting is reproducible rather than a dashboard click nobody remembers.
- Verify the upper tier is in the path by reading
CF-Raycolo codes at the origin andcf-cache-statusfrom multiple regions. - Recognize the failure modes — an over-specific cache key, Worker subrequests, origin allow-lists — that make the switch look like a no-op.
Prerequisites and environment setup
You need a zone on Cloudflare with the hostname proxied (the orange cloud), because tiering only applies to traffic that passes through the Cloudflare cache. A grey-clouded record resolves straight to your origin and no amount of cache configuration will change that.
Tooling:
curl --version # 7.76+ for the flags used below
jq --version # jq-1.6 or later
terraform version # 1.5+
Two credentials. CF_API_TOKEN needs the Zone → Zone Settings → Edit permission for the tiering endpoints and Zone → Cache Purge → Purge if you intend to purge during verification. ZONE_ID is on the zone overview page or from the API:
export CF_API_TOKEN="…"
export ZONE_ID=$(curl -s "https://api.cloudflare.com/client/v4/zones?name=example.com" \
-H "Authorization: Bearer $CF_API_TOKEN" | jq -r '.result[0].id')
echo "$ZONE_ID"
Entitlements differ by plan. Generic Tiered Cache is available broadly; Smart Tiered Cache Topology requires Argo (a paid add-on) or an Enterprise plan; Regional Tiered Cache is a separate paid entitlement. Check before you build config around something you cannot turn on.
Step-by-step procedure
Step 1 — Read the current tiering state and entitlement
Query the three settings. The editable field is the entitlement signal: false means the plan does not include it, regardless of the current value.
for path in \
"argo/tiered_caching" \
"cache/tiered_cache_smart_topology_enable" \
"cache/regional_tiered_cache"
do
echo "== $path"
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/$path" \
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result, .errors'
done
Expected output for a zone with generic tiering available but no Argo:
{
"id": "tiered_caching",
"value": "off",
"editable": true,
"modified_on": null
}
{
"id": "tiered_cache_smart_topology_enable",
"value": "off",
"editable": false
}
Step 2 — Record a baseline you can compare against
Do not skip this. Capture origin requests per minute and peak concurrent origin connections for a full weekday from your origin’s own metrics, plus the zone’s cache hit ratio from Cloudflare. Everything in measuring origin offload after enabling tiered caching depends on having this number before the change.
Step 3 — Choose the topology
If Smart Tiered Cache Topology is editable, use it: Cloudflare selects a single upper tier per zone based on measured latency to your origin, and it re-selects if you move the origin. If it is not available, generic tiering still collapses misses within each region and is worth enabling on its own. Add Regional Tiered Cache only when your traffic is genuinely spread across continents — with a single-region audience it adds a hop for little benefit.
Step 4 — Enable it
In the dashboard the controls live under Caching → Tiered Cache. Prefer the API so the change is scriptable and auditable:
# Generic tiered cache on
curl -sX 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"}' | jq '.success, .result.value'
# Smart topology on (requires Argo or Enterprise)
curl -sX PATCH \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/cache/tiered_cache_smart_topology_enable" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"value":"on"}' | jq '.success, .result.value'
A successful call returns:
{
"success": true,
"errors": [],
"result": {
"id": "tiered_cache_smart_topology_enable",
"value": "on",
"modified_on": "2026-07-28T09:14:22.418Z"
}
}
The Terraform equivalent, which is what should end up in your repository:
resource "cloudflare_tiered_cache" "example" {
zone_id = var.zone_id
cache_type = "smart" # "smart" | "generic" | "off"
}
resource "cloudflare_argo" "example" {
zone_id = var.zone_id
tiered_caching = "on"
smart_routing = "on"
}
resource "cloudflare_regional_tiered_cache" "example" {
zone_id = var.zone_id
value = "on"
}
The setting propagates across the network in well under a minute and requires no purge. Objects already cached at the edge stay cached; only the fill path for future misses changes.
Step 5 — Influence where the upper tier sits
Cloudflare does not expose a region field, so you influence tier placement in two ways. First, smart topology derives the tier from measured latency to whatever IP your origin record points at — so if your origin sits behind a global load balancer that advertises a far-away address, the topology follows the address, not the servers. Second, if you run multiple origins, publish the one nearest your primary write path as the proxied target; the apex and CNAME setup determines what the tier is measuring against.
Step 6 — Confirm from several regions
Pick one object that is cacheable and unlikely to be hot, then request it from three geographically separate hosts within a couple of minutes of each other. The first should be a MISS; the rest should be HIT without a corresponding origin log entry.
URL="https://www.example.com/assets/report-2026-q2.pdf"
for host in eu-runner us-runner ap-runner; do
ssh "$host" "curl -sI '$URL' | grep -iE 'cf-cache-status|cf-ray|age'"
done
Verification
Three independent signals, in increasing order of authority.
1. Client-side cache status. The cf-cache-status header reports the edge result only, so a tier fill still shows MISS on the first request in each region. What proves tiering is the pattern across regions plus the Age header: an object first fetched in Frankfurt and then requested in Singapore should come back with a non-zero Age that reflects the tier’s copy, not a fresh origin fill.
cf-cache-status: MISS
cf-ray: 8f2a1b3c4d5e6f70-FRA
age: 0
cf-cache-status: HIT
cf-ray: 9a3b2c4d5e6f7081-SIN
age: 47
2. The colo code your origin sees. This is the decisive local test. Cloudflare forwards a CF-Ray header to your origin, and its colo suffix identifies the machine that made the request. With tiering off, that suffix varies with visitor geography. With tiering on, it collapses to the upper tier’s colo for almost every request.
# On the origin, aggregate the colo suffix of CF-Ray across the last 10k requests
awk '{for (i=1;i<=NF;i++) if ($i ~ /^cfray=/) print substr($i, index($i,"-")+1)}' \
/var/log/nginx/access.log | tail -10000 | sort | uniq -c | sort -rn | head
# 9714 FRA
# 188 AMS
# 41 IAD
A single dominant colo is exactly what a working upper tier looks like. A flat spread of thirty colos means tiering is not in the path.
3. Logpush CacheTieredFill. On Enterprise, add the boolean field to an HTTP requests Logpush job. It is true when the edge filled from an upper tier rather than from origin, and it is the only field that answers the question directly.
{
"name": "http-requests-tiering",
"output_options": {
"field_names": [
"ClientRequestHost", "ClientRequestURI", "EdgeResponseStatus",
"CacheCacheStatus", "CacheTieredFill", "EdgeColoCode"
],
"timestamp_format": "rfc3339"
},
"dataset": "http_requests"
}
Troubleshooting
Origin load does not change at all
The most common cause is that the two tiers are not computing the same cache key, so nothing collapses. Check whether requests for the same object differ in query string, cookie, or a Vary-driven dimension. Count distinct keys per path in your edge logs; if one URL produces hundreds of keys, tiering cannot help until the key is normalized as described in customizing cache keys to improve hit ratio. The second cause is simpler: the response was never cacheable. cf-cache-status: DYNAMIC or BYPASS means the edge refused to store it, and an upper tier cannot cache what the edge will not.
The cache key is too specific for the tier to be useful
A key that includes anything varying by PoP — CF-IPCountry, a device-class header, a geolocation cookie — fragments the tier’s storage exactly as it fragments each edge’s. Inspect the Cache Rule:
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_request_cache_settings/entrypoint" \
-H "Authorization: Bearer $CF_API_TOKEN" \
| jq '.result.rules[] | {description, cache_key: .action_parameters.cache_key}'
Remove per-visitor dimensions, or collapse them to a low-cardinality flag before they enter the key.
Purge behaves differently than expected
Purges propagate to the upper tier as well as to the edges, so a purge does not leave a stale copy at the parent. What changes is refill: after a purge, edges refill from the tier and only the tier goes to origin. If an object still looks stale after a purge, confirm you purged the exact cache key including the variant — a URL purge does not cover a key you customized to include a header. Tag-based purging avoids the problem entirely and is covered in purging Cloudflare cache via API on deploy.
Worker subrequests skip the tier
A fetch() from a Worker to your origin is a new subrequest. Depending on how it is issued it may bypass tiering and go direct, which is why a zone that routes most traffic through a Worker can show almost no offload change. Route the subrequest through the cache so it participates in tiering:
export default {
async fetch(request, env, ctx) {
// cacheEverything makes the subrequest participate in the tiered cache path
return fetch(request, {
cf: {
cacheEverything: true,
cacheTtlByStatus: { "200-299": 3600, "404": 60, "500-599": 0 },
},
});
},
};
Confirm the effect with wrangler tail while replaying a request, and check that the subrequest’s response carries a cache status rather than always reporting a fresh origin fetch.
TLS or origin authentication breaks after enablement
The connection your origin now receives comes from a different set of Cloudflare machines than before. Three things break in practice: an origin firewall allow-list scoped to a subset of Cloudflare ranges, mutual TLS pinned to a specific client certificate chain, and any origin logic that keys on source IP for rate limiting. Allow the provider’s complete published address ranges, use Authenticated Origin Pulls rather than IP-based trust, and key any origin-side throttling on a forwarded header instead of the socket address.
Frequently Asked Questions
Do I need Argo to use Tiered Cache on Cloudflare?
Generic Tiered Cache is available without Argo and already collapses misses within a region. Smart Tiered Cache Topology — the version that picks a single upper tier by measured latency to your origin — requires Argo or an Enterprise plan. Read the editable field on the setting to see what your zone actually has.
Can I choose which Cloudflare colo acts as the upper tier?
No. Unlike CloudFront’s OriginShieldRegion or a Fastly shield POP code, Cloudflare selects the tier for you. Smart topology bases that choice on latency to the address your proxied record points at, so the practical lever is where your origin address resolves, not a config field.
Will enabling Tiered Cache invalidate anything already cached? No. It changes the fill path for future misses only, so objects currently cached at the edge keep serving. That also means the offload improvement appears gradually as objects expire and refill through the tier rather than instantly.
Why does cf-cache-status still say MISS after I turned tiering on?
Because that header describes the edge, not the fill source. A MISS served in 30 ms with a non-zero Age was almost certainly filled from the upper tier. Use the CF-Ray colo distribution at your origin, or the CacheTieredFill Logpush field, to see where the fill actually came from.
Is it safe to turn Tiered Cache off again? Yes, and quickly. It is a routing setting, not a content one, so disabling it needs no purge. Expect a brief rise in origin requests as edges resume filling directly, and keep any cache-key normalization you did on the way in — that work is valuable whether or not a tier exists.
Related
- Measuring Origin Offload After Enabling Tiered Caching
- Customizing Cache Keys to Improve Hit Ratio
- Purging Cloudflare Cache via API on Deploy
Back to Tiered Caching & Origin Shield