Setup guide

Connecting Cloudflare R2 to an S3 desktop client

Published 2026-09-17 · Updated 2026-09-17

R2 speaks the S3 protocol, so any S3 client can use it — once you get the endpoint right. Here is each field, and what the 403 that looks like a broken key really means.

Cloudflare R2 speaks the S3 protocol, which means any S3 client can talk to it — once you give it four pieces of information in the right shape. Three of them are easy. The fourth, the endpoint, is where most people get stuck, and there is a 403 error further along that looks like a broken key but is not.

This walks through the whole thing, including what to do when the connection test fails.

What you need before you start

Four values. Collect all four before opening your client:

ValueWhere it comes from
Endpoint URLBuilt from your Cloudflare account ID
Access Key IDShown when you create an R2 API token
Secret Access KeyShown once, at token creation
RegionAlways auto for R2

Step 1: create an API token

In the Cloudflare dashboard, go to R2APIManage API Tokens, and create a token. Two choices matter here.

Permission. Object Read only if the client just needs to download and browse. Object Read & Write if it will upload or delete. Do not reach for admin permissions — a client that manages objects does not need to create or destroy buckets.

Scope. You can allow the token to reach every bucket in the account, or restrict it to specific buckets. Restricting is the better habit, and it has a consequence covered below that catches people out.

The secret is shown once. When the token is created, Cloudflare displays the secret access key a single time. Copy it before closing the dialog. There is no way to retrieve it afterwards — if you lose it, you delete the token and make a new one.

Step 2: build the endpoint URL

This is the step that trips people up, because the value you need is not labelled "endpoint" in an obvious place. The form is:

https://<your-account-id>.r2.cloudflarestorage.com

Your account ID is the 32-character hexadecimal string visible on the R2 overview page (and in your dashboard URL). So a complete endpoint looks like https://ae58c4ef843fd8730cfaa2d0fd8904a5.r2.cloudflarestorage.com.

Three things people get wrong here:

Step 3: fill in the client

With the four values in hand, the connection settings are straightforward:

A connections screen listing saved storage connections side by side, each card showing the provider, region, number of buckets and when the connection was last tested.
R2, S3 and a self-hosted endpoint can all sit side by side as separate saved connections.

When the test fails: reading the error

A good client tests the connection in stages, which makes the failure point obvious. Here is what each failure actually means.

The hostname does not resolve

The account ID in the endpoint is wrong, or there is a typo. Compare it character by character against the dashboard.

InvalidAccessKeyId

The access key ID is not recognised. Usually a copy-paste that grabbed a trailing space, or a token that has since been deleted.

SignatureDoesNotMatch

The secret does not match the key ID. Also what you get if the region field is empty — put auto in it.

AccessDenied on ListBuckets — the one that looks worse than it is

This is the error worth understanding, because it reads like a broken credential and is not one.

If you scoped the token to specific buckets in step 1, that token cannot list buckets. Listing is an account-level operation, and a bucket-scoped token has no account-level permission. So the client asks "what buckets are here?", gets a 403, and may report the whole connection as failed.

A 403 proves the signature worked. The server had to authenticate your request before it could decide to refuse it. If your credentials were wrong you would get InvalidAccessKeyId or SignatureDoesNotMatch instead. AccessDenied means "I know who you are, and you may not do this" — which, for a bucket-scoped token asking to list buckets, is exactly correct behaviour.

The fix is to tell the client which bucket to open, usually a "default bucket" field on the connection. With that filled in, the client goes straight to the bucket it is allowed to touch and everything works normally — browsing, uploading, downloading and sharing all operate at the object level, which the token permits.

If your client offers no such field and insists on listing, you have two options: widen the token to cover all buckets in the account, or use a client that handles scoped tokens. Widening a token to work around a client limitation is the worse trade.

Two R2 details that differ from S3

Storage classes barely apply

R2 has Standard and Infrequent Access, and that is it. The S3 class names — Glacier, Deep Archive, Intelligent-Tiering — do not exist here. If your client shows the full S3 list, most of those options will simply be rejected.

Public access is a separate, deliberate step

Making an R2 bucket readable on the web is done in the dashboard, not through the S3 API. Cloudflare then gives you a pub-<random>.r2.dev address or lets you attach a custom domain. That address cannot be discovered through the API — no S3 call reports it — so any tool that offers to copy public links has to be given it once.

A working checklist

  1. Token created with Object Read or Object Read & Write, not admin.
  2. Secret copied before closing the creation dialog.
  3. Endpoint is https://<account-id>.r2.cloudflarestorage.com with no bucket appended.
  4. Region set to auto, not left blank.
  5. Addressing style set to path-style.
  6. If the token is bucket-scoped, the bucket name is filled into the client's default-bucket field.

Common questions

What is my Cloudflare account ID?

A 32-character hexadecimal string shown on the R2 overview page in the dashboard, and visible in the dashboard URL. It is the only variable part of the R2 endpoint address.

Why does my R2 connection fail with AccessDenied on ListBuckets?

Because the API token is scoped to specific buckets, and listing buckets is an account-level operation such a token cannot perform. The credentials are fine — a 403 means the server authenticated you and then refused this particular call. Name the bucket in the client's default-bucket setting and it will open directly.

Should the region be auto or blank?

Always auto. R2 ignores the value, but the S3 signing process requires a non-empty region, so leaving it blank produces SignatureDoesNotMatch.

Path-style or virtual-hosted addressing for R2?

Path-style. Virtual-hosted requests, which put the bucket name in the hostname, will not resolve against the R2 endpoint.

Can I recover a lost R2 secret access key?

No. Cloudflare shows it only at creation. Delete the token and create a new one, then update any client using it.

Related guides

About BucketBay. BucketBay is a desktop app for S3-compatible object storage — Amazon S3, Cloudflare R2, MinIO, Backblaze B2, Wasabi and anything else that speaks the protocol. It does the things described above without a browser tab or a command line. View it in the Microsoft Store.