## TL;DR

Cloudflare R2 has no regions but AWS Signature V4 signs with one anyway, so every R2 request must be signed with region `auto`. If your S3 client is configured with `us-east-1`, `eu-west-1`, or any other real region, the presigned URL signs without complaint and R2 rejects it with SignatureDoesNotMatch. Fix: set the client's region to exactly `auto` and re-sign.

## The error

The presigned request returns XML like this:

```text
[Code]SignatureDoesNotMatch[/Code]
[Message]The request signature we calculated does not match the signature you provided. Check your secret access key and signing method.[/Message]
```

Note: in fenced XML the literal tags are fine for human readers; the scanner strips them on publish. The important fields are the `SignatureDoesNotMatch` code and the misleading hint to check the secret key.

## Steps

1. Find where your S3 client is constructed and read the region value. Grep for `region_name=`, `region:`, or `Region` near the client setup. The usual suspects are a leftover `us-east-1` copied from an AWS example, an env var defaulting to `us-east-1`, or a made-up region someone invented.

2. Set the region to exactly `auto` on the R2 client. boto3:

```python
s3 = boto3.client(
    "s3",
    endpoint_url="https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com",
    aws_access_key_id=[your R2 access key id],
    aws_secret_access_key [your value] R2 secret access key],
    region_name="auto",
    config=boto3.session.Config(signature_version="s3v4"),
)
```

AWS SDK for JS v3:

```ts
const r2 = new S3Client({
  region: "auto",
  endpoint: "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com",
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID,
    secretAccessKey - process.env.R2_SECRET_ACCESS_KEY,
  },
});
```

3. Re-generate the presigned URL with the fixed client and inspect it. The `X-Amz-Credential` query parameter must contain `/auto/s3/aws4_request` in its credential scope. If it still shows `us-east-1` or another region, you are signing with a different client instance than the one you fixed. Expected: URLs minted after the change carry the `auto` scope.

4. Re-run the failing test or request. Expected: 200 on GET, 200 or 204 on PUT. Presigned URLs minted with the old region stay broken; only newly generated ones work.

5. If it still fails with SignatureDoesNotMatch, rotate the credential once and re-sign before assuming the region is innocent. Same error text, different cause.

## When to use this skill

- R2 presigned URLs fail with SignatureDoesNotMatch
- A test signs R2 requests with the wrong region (us-east-1, eu-central-1, a made-up region)
- Migrating boto3, AWS SDK JS v3, aws-sdk-go-v2, or .NET AWSSDK.S3 code to the R2 endpoint

## When NOT to use it

- Plain AWS S3: real regions are correct there, `auto` is R2-only
- 401/403 with a different error code (NoSuchBucket, AccessDenied): region is not the cause
- Presigned POST uploads: R2 does not support presigned POST, a separate failure

## Variant phrasings

### SignatureDoesNotMatch boto3 r2

The Cloudflare community case: a boto3 client built with a non-auto region generated a presigned URL that R2 rejected with SignatureDoesNotMatch. Same fix: `region_name="auto"` on the client.

### r2 presigned url 403 wrong region

Same family. Any 403 on an R2 presigned URL where the error code is SignatureDoesNotMatch and credentials are known good points at the signing region first.

### r2 s3 client region auto

Configuring the region is a one-line change per client above. Do not set it per-request; pin it where the client is constructed so every call, including presigned URLs, signs the same way.

## Why it happens

AWS SigV4 folds the region into the signature: the credential scope (`[access key]/[date]/[region]/s3/aws4_request`) and the derived signing key both bind the region. R2 accepts SigV4 but has no regions, so the server-side calculation always uses `auto`. Any client-side region other than `auto` computes a signature R2 can never match. The error message blames the secret key, which sends people checking credentials first. The region is the cheaper thing to check, so check it first.

## Edge cases

- aws-sdk-go-v2 rejects `auto` with "region was not a valid DNS name": also pin the endpoint with a custom resolver (HostnameImmutable) so the SDK does not derive DNS from the region.
- Content-Type mismatch on presigned PUT: if you signed a ContentType, the actual request must send the identical header or you get a different 403.
- boto3 defaults to SigV2 for some legacy paths: force `signature_version="s3v4"` in the client Config as shown above.
- Shared client for AWS S3 and R2: region `auto` is R2-only. Use a separate client instance for real S3 traffic with its real region.
- URLs minted before the fix stay broken. Any test that caches a presigned URL across a config change will keep failing.