Skip to main content

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.