Fix S3 SignatureDoesNotMatch and AWS4-HMAC-SHA256 Errors in Django

Fix SignatureDoesNotMatch and The authorization mechanism you have provided is not supported, please use AWS4-HMAC-SHA256 in django-storages: set the bucket region and signature version, fix presigned URLs, clock skew, and custom endpoints for R2, Spaces, and MinIO.

Problem statement

Uploads fail, or images load locally but not in production, with one of these:

botocore.exceptions.ClientError: An error occurred (SignatureDoesNotMatch) when calling
the PutObject operation: The request signature we calculated does not match the
signature you provided. Check your key and signing method.
<Error><Code>InvalidRequest</Code><Message>The authorization mechanism you have
provided is not supported. Please use AWS4-HMAC-SHA256.</Message></Error>

Every S3 request is signed with your secret key, the region, the time, and details of the request. S3 recomputes the signature and compares. A mismatch means one of those inputs differs between your app and S3: the wrong secret, the wrong region, an old signature version, a clock that is off, or a URL that was changed after signing.

Why S3 rejects the signatureDjango signskey, region, timeRequest sentheaders and URLS3 recomputeswith its own viewMismatchSignatureDoesNotMatchRegion, signature version, clock, and the exact URL all feed into the signature.
  1. Set the bucket's real region: AWS_S3_REGION_NAME = "eu-central-1".
  2. Force Signature Version 4: AWS_S3_SIGNATURE_VERSION = "s3v4".
  3. Check the secret key for stray whitespace or a rotated value: test with aws s3 ls s3://bucket --profile app.
  4. Make sure the server clock is synced: timedatectl should show System clock synchronized: yes.
  5. For R2, Spaces, or MinIO, set AWS_S3_ENDPOINT_URL and the provider's region value.

Step-by-step solution

1. Set the region and signature version

Regions launched after 2014, such as eu-central-1, ap-south-1, and ca-central-1, accept only Signature Version 4, and v4 signatures include the region. A missing or wrong region produces both errors above, especially for presigned URLs:

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "bucket_name": "myapp-media",
            "region_name": "eu-central-1",
            "signature_version": "s3v4",
        },
    },
}

Find the bucket's region with aws s3api get-bucket-location --bucket myapp-media. A null location means us-east-1.

2. Verify the credentials themselves

AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... aws s3 ls s3://myapp-media --region eu-central-1

If the CLI fails with the same error, the secret is wrong. Common causes: a trailing newline or space copied into an environment variable, a key rotated in IAM but not in the app, or quotes included literally in a .env file parsed by a tool that does not strip them.

match the symptomSymptomCause and fixuse AWS4-HMAC-SHA256v2 or no region; s3v4 and region_nameCLI fails with same keysecret wrong or haswhitespace; reset itRequestTimeTooSkewed tooserver clock off; enable NTP synconly presigned URLs failURL changed or wronghost; sign final URLTest the same credentials with the AWS CLI to split key problems from configdjango-deployment.com
If the CLI works and Django does not, the difference is configuration.

3. Check the clock

Signatures include a timestamp, and S3 rejects requests more than 15 minutes off. Containers use the host clock:

timedatectl
sudo timedatectl set-ntp true

A skewed clock usually also shows RequestTimeTooSkewed, but some clients surface it as a signature mismatch.

4. Do not modify presigned URLs

A presigned URL's signature covers its host, path, and query string. Rewriting the host to a CDN, re-encoding the path, or adding query parameters breaks it. If you serve media through CloudFront, use custom_domain with public objects or CloudFront signed URLs, not S3 presigned URLs with the domain swapped:

"OPTIONS": {
    "bucket_name": "myapp-media",
    "region_name": "eu-central-1",
    "querystring_auth": True,         # presigned S3 URLs
    # "custom_domain": "media.example.com",  # only for public objects via CDN
}

Also, file names with spaces or unicode must be encoded once, by boto3. Pre-encoding names in your code leads to double encoding and a mismatch.

5. S3-compatible providers

Cloudflare R2, DigitalOcean Spaces, Backblaze B2, and MinIO use the S3 API with their own endpoints:

"OPTIONS": {
    "bucket_name": "myapp-media",
    "endpoint_url": "https://<account>.r2.cloudflarestorage.com",
    "region_name": "auto",           # R2; Spaces uses e.g. "fra1"
    "signature_version": "s3v4",
    "addressing_style": "virtual",   # MinIO often needs "path"
}

Using the AWS default endpoint with R2 or Spaces keys gives InvalidAccessKeyId or a signature mismatch.

every layer must match on both sidesRequest URL and headershost, path, queryTimestampwithin 15 minutesRegion and servicee.g. eu-central-1/s3Secret access keyexact charactersA change in any layer changes the signaturedjango-deployment.com
Most fixes are in the middle two layers: region and clock.

Explanation

Signature Version 4 derives a signing key from the secret, the date, the region, and the service, then signs a canonical form of the request. S3 does the same from its side. Any difference produces SignatureDoesNotMatch; S3 cannot tell you which input differed, only that the result did. The AWS4-HMAC-SHA256 message is more specific: the client used the older v2 scheme, which newer regions do not accept. Recent boto3 versions default to v4, but explicit old settings or an unknown region can still produce v2-style or wrongly scoped requests.

Edge cases / notes

  • Bucket names with dots. Virtual-hosted addressing over HTTPS fails certificate checks for buckets like media.example.com; use path addressing or rename the bucket.
  • Content-Type changes. Presigned uploads signed with a Content-Type must be sent with exactly that header from the browser.
  • IAM roles. On EC2, ECS, or EKS, prefer instance or task roles over static keys; they avoid copied-secret problems entirely.

Next steps

FAQ

What does "please use AWS4-HMAC-SHA256" mean?

The request was signed with the old Signature Version 2. Set signature_version to s3v4 and the correct region.

Links are often signed with a different region or then altered, for example by swapping the host for a CDN domain.

Can clock drift really cause this?

Yes. Signatures include the time, and S3 rejects requests far outside the allowed window.

Do I need region_name for S3-compatible providers?

Yes, use the value the provider documents, such as auto for R2.

Keep going