# Cloudflare R2

> Store your SFTP server's files in your own Cloudflare R2 bucket, with no egress fees.

Updated 2026-10-22

R2 doesn't charge for egress, so it suits servers where partners download a lot. You can also pin a bucket to the EU jurisdiction.

## Before you start

- A Cloudflare account with R2 enabled.
- Your **Account ID**: a 32-character hex string, shown on the R2 **Overview** page under **Account Details**. It's also the first part of the bucket's **S3 API** address.

## 1. Create the bucket

1. In the Cloudflare dashboard, go to **R2 object storage → Overview** and select **Create bucket**.
2. Pick the **Location**:
   - **Automatic** or a **location hint** (for example *Eastern Europe (EEUR)*) only suggests where the data should live. It is not a guarantee, and the bucket uses the default endpoint.
   - A **jurisdiction** (for example *European Union*) guarantees the data stays there, and the bucket gets its own endpoint (see step 3). Choose this if your contracts require data to stay in the EU. It can't be changed after the bucket is created.
3. Create the bucket.

### Keep the bucket private

In the bucket's **Settings**, leave **Public Development URL** disabled and don't connect a **Custom Domain**. Either one makes every file readable by anyone who guesses its path. CloudFile doesn't need them: download links in webhooks are signed and expire after an hour.

You can leave the other settings as they are. The default lifecycle rule that aborts unfinished multipart uploads after 7 days is fine. To upload files over 100 MB from the web file manager or through file requests, add a CORS policy that allows `PUT` from `https://cloudfile.dev` under **Settings → CORS Policy** (see [Uploads from the browser](/docs/storage#uploads-from-the-browser-cors)). SFTP and FTP don't need it.

## 2. Create an API token

R2 access keys come from an R2 API token scoped to your bucket.

1. On the R2 **Overview** page, under **Account Details**, select **Manage** next to **API Tokens**.
2. Choose **Create Account API token**. (A user token works too, but stops working if your user leaves the account.)
3. Under **Permissions**, choose **Object Read & Write**.
4. Under **Specify bucket(s)**, choose **Apply to specific buckets only** and pick your bucket.
5. Create the token. Cloudflare shows a summary with three values, once:
   - **Token value** (starts with `cfat_`): not needed by CloudFile.
   - **Access Key ID** (32 characters): goes into CloudFile.
   - **Secret Access Key** (64 characters): goes into CloudFile.

   The summary also lists the endpoint to use under **Use jurisdiction-specific endpoints for S3 clients**.

Copy the Access Key ID and Secret Access Key somewhere safe. Cloudflare won't show the secret again. If they leak, open the token and select **Roll** to get new keys, then update them in CloudFile.

## 3. Connect it

In CloudFile, go to **Storage → Add bucket** and choose **Cloudflare R2**:

| Field | Value |
| --- | --- |
| Bucket | Your bucket name, e.g. `my-files` |
| Account ID | From **Account Details**, e.g. `9276e14e21f297b9b1b0779e028b6814` |
| Endpoint | Filled in from the Account ID: `https://<account-id>.r2.cloudflarestorage.com` |
| Access key ID | The Access Key ID from step 2 |
| Secret access key | The Secret Access Key from step 2 |
| Name | Optional, e.g. `Production` |
| Path-style URLs | On |

The endpoint never includes the bucket name. The bucket's settings page shows its **S3 API** address as
`https://<account-id>.r2.cloudflarestorage.com/my-files`. If you paste that, CloudFile drops the `/my-files` part for you.

For a bucket in a jurisdiction, add the jurisdiction to the endpoint, for example `https://<account-id>.eu.r2.cloudflarestorage.com` for the EU. A location hint such as EEUR doesn't change the endpoint. The region is always `auto`, so CloudFile sets it for you.

Click **Test connection**. CloudFile writes, reads and deletes a small test file. Then click **Save**.

## 4. Use it on a server

Open a server and pick the bucket under **Settings → Storage**. New files go to the bucket from then on. Files already on the server aren't moved.

In the bucket, each SFTP user gets a folder, `<prefix>/<username>/`, and shared folders live in `<prefix>/_shared/<name>/`. Leave the prefix empty to put them at the bucket root.

## Troubleshooting

- **Writing a test file failed: The specified bucket does not exist.** Either the bucket name is wrong, or the bucket is in a jurisdiction (for example the EU) and the endpoint is missing it. Use `https://<account-id>.eu.r2.cloudflarestorage.com`.
- **Writing a test file failed** with a DNS or connection error. The Account ID is wrong. Copy it again from **Account Details**. It's a 32-character hex string.
- **Writing a test file failed: Access Denied.** The token is scoped to a different bucket, or it has only **Object Read** permission. Create a new token with **Object Read & Write** for this bucket.
- **Writing a test file failed** with an invalid access key or signature error. You pasted the `cfat_…` token value instead of the Access Key ID and Secret Access Key shown with it, or the token was rolled since.

See [Your own bucket](/docs/storage) for how files are laid out in the bucket.
