Public Access and Custom Domains
To serve R2 assets to end-users (like images on a website), you must route traffic through Cloudflare's CDN using Custom Domains.
The Request Flow
Understanding the architecture is crucial for debugging and performance tuning.
sequenceDiagram
participant User as Browser
participant WAF as Cloudflare WAF
participant Cache as Edge Cache
participant R2 as R2 Bucket
User->>WAF: GET https://assets.example.com/logo.png
WAF-->>User: Blocked (If malicious IP/Rule Match)
WAF->>Cache: Passed WAF Rules
alt Cache HIT
Cache-->>User: Serve logo.png (Fastest, No R2 fee)
else Cache MISS
Cache->>R2: Fetch logo.png
R2-->>Cache: Return Data (Billed as Class B read)
Cache-->>User: Serve and Store in RAM
end
Connecting a Custom Domain
Using the dashboard is simple, but Infrastructure as Code is better.
Via Dashboard: R2 -> Settings -> Public Access -> Connect Domain. Via Wrangler CLI:
wrangler r2 bucket domain add production-assets --domain assets.example.com
Securing the Public Domain (WAF Rules)
Once connected, assets.example.com is public. To prevent hotlinking or malicious scraping, you must implement Web Application Firewall (WAF) rules in the Cloudflare Dashboard under Security -> WAF.
Example 1: Prevent Hotlinking
Block requests where the Referer header doesn't match your domain.
- Expression:
(http.host eq "assets.example.com" and not http.referer contains "yourwebsite.com") - Action:
Block
Example 2: API Token Requirement
If your frontend app dynamically fetches assets, require a custom header.
- Expression:
(http.host eq "assets.example.com" and http.request.headers["x-asset-token"] ne "super-secret-key") - Action:
Block
Cache Invalidation (Purging)
When you overwrite an object in R2 (e.g., updating logo.png), the Edge Cache does not automatically clear. Users will still see the old logo until the Cache-Control max-age expires.
You must manually purge the cache via the Dashboard or API.
Purging via API (cURL)
curl -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/purge_cache" \
-H "Authorization: Bearer <CLOUDFLARE_API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"files":["https://assets.example.com/logo.png"]}'
Best Practice: Append content hashes to filenames (e.g., logo.v123.png) in your build process to avoid caching issues entirely, rather than relying on manual purges.