Skip to content

Cloudflare R2

Use a Cloudflare R2 bucket as a realm's storage.

storage:
  provider: r2
  endpoint: https://<account id>.r2.cloudflarestorage.com   # no bucket in it
  bucket: acme-files
  prefix: prod
  access_key: secret:STORAGE_ACCESS_KEY
  secret_key: secret:STORAGE_SECRET_KEY
Endpointhttps://<account id>.r2.cloudflarestorage.com (also .eu. and .fedramp. for those jurisdictions). R2’s console shows it with the bucket’s name after it: write only the host, and the name in bucket.
Addressingvirtual-hosted
Regionalways auto
Signed SHA-256 (direct uploads)supported: R2 rejects an upload whose digest differs (confirmed by the provider smoke test)
Largest single PUT5 GiB
Multipart uploads (files above 5 GiB)supported, with a SHA-256 per part (confirmed by the provider smoke test)
backd storage check readsneither CORS nor encryption: it says so

Setting it up

  1. Create the bucket in the Cloudflare dashboard (R2 → Create bucket). Keep it private: don’t enable public access or a public domain.
  2. Create an API token (R2 → Manage API tokens) with Object Read & Write limited to that bucket. R2 shows an Access Key ID and a Secret Access Key once: those are the two secrets.
  3. Encryption at rest: R2 encrypts all objects at rest; there is nothing to set.
  4. CORS, needed for direct uploads and cross-origin downloads: set it on the bucket (Settings → CORS policy) for your app’s origin, with GET, PUT and HEAD and the allowed headers Content-Type and x-amz-checksum-sha256. backd storage check can’t read it: try an upload from the app.
  5. Set the secrets and run backd storage check --realm <realm>.

R2’s tokens limit the bucket, not a prefix: the prefix still keeps each backd instance’s files apart, but a key can reach the whole bucket, so give each instance sharing a bucket a token of its own only if you must trust no instance with another’s files; otherwise use a bucket per instance.

The checksum

Direct uploads depend on the storage verifying a signed x-amz-checksum-sha256, and R2 does: the provider smoke test (go test ./internal/storage -run R2, which needs a bucket and a token of your own and uploads a handful of tiny objects) confirmed that a wrong digest is rejected and the stored one is reported back. backd storage check reports it on your bucket: signed SHA-256: verified or ignored.

Edit this page · Last updated 08 Oct 2026