Configuration / Serving and Caching Settings
R2 Page Origin Caching
Mirroring pages to R2 moves them off the Worker, and an R2 public bucket on a custom domain serves them. How much of that traffic Cloudflare's CDN absorbs in front of the bucket decides whether the off-Worker lever is worth anything: an absorbed read costs neither a Worker request nor an R2 Class B operation.
Absorption is 0 without a Cache Rule and about 7/8 with one. Measured with GET against a cold object each time:
| Condition | Result |
|---|---|
| Cache Rule on, cold object | MISS, then 7 / 7 HIT |
| Cache Rule off, cold object | MISS, then 7 / 7 DYNAMIC |
A second object under the same rule read 1 MISS, 7 HIT and 2 DYNAMIC across 10 requests, which is the same picture with two requests landing at a PoP that had not filled yet.
Measure with GET, never curl -I. HEAD is what curl -I sends, Cloudflare does not populate its
cache from a HEAD, and every reading comes back DYNAMIC.
The Cache Rule
One rule, scoped to the CDN hostname and nothing else:
expression: (http.host eq "drupflare-cdn.example.com")
action: set_cache_settings
action_parameters: { "cache": true, "edge_ttl": { "mode": "override_origin", "default": 3600 } }In the dashboard it is Caching > Cache Rules > Create rule, with the hostname as the only
condition and "Eligible for cache" plus an edge TTL override. Over the API it is
PUT /zones/{ZONE_ID}/rulesets/phases/http_request_cache_settings/entrypoint.
A PUT to an entrypoint replaces every rule in it. Read the entrypoint first and merge; on a zone that has never had a cache-phase ruleset the entrypoint does not exist yet and creating it destroys nothing.
One zone-scoped permission creates it: Zone > Cache Rules > Edit. Cloudflare's documentation
also lists the account-scoped Account Rulesets: Edit and Account Filter Lists: Edit. This rule
shape needs neither.
Smart Tiered Cache
Every PoP fills independently, so a request reaching a cold PoP is a MISS and an R2 Class B read. Tiering routes a PoP miss through an upper tier instead of to the bucket, which changes absorption from per-PoP to hierarchical.
Enable it at Caching > Tiered Cache, or with the API:
Tiered Cache API command
curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/cache/tiered_cache_smart_topology_enable" \
--request PATCH \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--json '{ "value": "on" }'The token needs Zone Settings Write or Zone Write. Tiered Cache and Smart Topology are both
available on the Free plan; Generic Global, Regional and Custom topologies are Enterprise only.
Cloudflare selects the upper tier automatically for a zone using R2 as an origin.
Absorption in the Model
cdnAbsorption in scripts/measure/free-envelope.ts defaults to 0.
Absorption follows the traffic. It depends on reads per colo per TTL window, so a site with few views spread over many PoPs absorbs almost nothing, and the same site at high traffic absorbs nearly everything. Zero is the floor for a low-traffic site and for any site with no rule.
cdnAbsorption is a code-level option. The command line takes --visits, --dynamic and --warmth
only; absorption is passed to envelope(), optimalOffWorker() and scoreWorkload() in
scripts/measure/free-envelope.ts.
Drupflare does not create the Cache Rule. It changes a zone the operator owns, and the model's default stays at 0 until an operator applies the rule.