R2 presigned URL fails with SignatureDoesNotMatch: the signing region must be auto
Cloudflare R2 presigned URLs and S3 SDK calls fail with SignatureDoesNotMatch when the client is configured with a real region like us-east-1 instead of auto. R2 has no regions but SigV4 signs with one, so every R2 request must be signed with region auto. Use this skill when boto3, AWS SDK JS v3, aws-sdk-go-v2, or .NET SDK code against the R2 endpoint throws SignatureDoesNotMatch, or when a test signs R2 presigned URLs with the wrong region.
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:
[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
- Find where your S3 client is constructed and read the region value. Grep for
region_name=,region:, orRegionnear the client setup. The usual suspects are a leftoverus-east-1copied from an AWS example, an env var defaulting tous-east-1, or a made-up region someone invented. - Set the region to exactly
autoon the R2 client. boto3:
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:
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,
},
});- Re-generate the presigned URL with the fixed client and inspect it. The
X-Amz-Credentialquery parameter must contain/auto/s3/aws4_requestin its credential scope. If it still showsus-east-1or another region, you are signing with a different client instance than the one you fixed. Expected: URLs minted after the change carry theautoscope. - 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.
- 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,
autois 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
autowith "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
autois 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.