Introduction

An S3 presigned URL is valid only for a specific HTTP method, object key, region, credential set, and time window. If any of those inputs change between signing and request execution, S3 rejects the request with SignatureDoesNotMatch, AccessDenied, or an expiration error. The most reliable fix is to compare the exact request being made against the exact request that was signed.

Symptoms

  • S3 returns SignatureDoesNotMatch
  • The client sees Request has expired or AccessDenied
  • The URL works immediately after generation but fails after proxying or browser upload retries
  • Downloads succeed while uploads fail, or vice versa

Common Causes

  • The client signs with the wrong AWS region for the bucket
  • The request method, headers, or content type differ from what was signed
  • The expiration window is too short for the actual client flow
  • Clock skew on the signing host or client invalidates the timestamp

Step-by-Step Fix

  1. 1.Confirm the exact method and object details used when generating the URL
  2. 2.A URL signed for GET will not work for PUT, and a different key or bucket region invalidates the signature immediately.

```python import boto3

s3 = boto3.client("s3", region_name="us-east-1") url = s3.generate_presigned_url( "get_object", Params={"Bucket": "my-bucket", "Key": "file.txt"}, ExpiresIn=3600, ) ```

  1. 1.Verify the bucket region matches the signing client region
  2. 2.Presigned URLs are region-sensitive. A bucket in eu-west-1 signed with an us-east-1 client often fails with a signature mismatch.
bash
aws s3api get-bucket-location --bucket my-bucket
  1. 1.Check whether the client changed signed headers or content type
  2. 2.Upload flows often break because the browser or proxy adds a different Content-Type, host, or signed header set than the presigner expected.
bash
curl -v -X PUT \
  -H "Content-Type: image/jpeg" \
  --upload-file test.jpg \
  "https://..."
  1. 1.Reduce clock skew and choose a realistic expiration
  2. 2.If the URL is generated on a host with drift or given a too-short lifetime, the request may already be invalid by the time the client uses it.
bash
timedatectl status

Prevention

  • Sign URLs with the bucket's actual AWS region, not a guessed default region
  • Keep the HTTP method and signed headers stable between generation and use
  • Use expiration values that match the real upload or download experience
  • Monitor system clock drift on servers that mint presigned URLs