AWS S3 Generating Signed Urls ''AccessDenied''
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
A presigned S3 URL only works if the request created by the client matches what was signed and the signing credentials are actually allowed to perform that S3 operation. When the browser or SDK gets AccessDenied, the problem is usually not that signing failed. It is usually a permissions, region, method, or header mismatch issue.
What a Presigned URL Really Does
A presigned URL lets someone perform a specific S3 operation for a limited time without holding AWS credentials directly. The URL is created using the signer's credentials, so S3 still evaluates the signer's permissions when the URL is used.
That means a presigned URL does not bypass IAM. If the principal that created the URL cannot GetObject or PutObject on that bucket and key, the presigned request will still fail.
The Most Common Root Cause: Missing Permission
For a download URL, the signing identity needs permission such as s3:GetObject. For an upload URL, it usually needs s3:PutObject.
Example IAM policy for downloads:
If the key being signed falls outside that resource path, the generated URL will still be created locally, but S3 will reject it at request time.
A Correct boto3 Download Example
This is the standard Python pattern for generating a presigned download URL.
If using that URL returns AccessDenied, check the signer, bucket policy, object key, and region before changing the code.
Region Mismatch Matters
S3 signing is region-sensitive. If the client is created for the wrong region, the URL may be signed against the wrong endpoint and the request can fail.
Make sure the bucket region and client region align:
If you are unsure, inspect the bucket region first with get_bucket_location or in the AWS console.
Upload URLs Must Match the Signed Request
For presigned uploads, the caller must send the same method and relevant headers that were assumed when signing. A common example is signing a put_object URL and then using the wrong Content-Type or an unexpected extra header in the actual upload request.
If the client later uploads with a different content type, the signature may no longer match and S3 can reject the request.
Bucket Policies Can Still Deny the Request
Even if the signer has IAM permission, the bucket policy can still block access. Typical examples include:
- denying requests outside a VPC endpoint
- requiring server-side encryption headers
- restricting access by source IP
- denying public-style requests to specific prefixes
So the mental model should be: presigning authenticates the request, but S3 still evaluates the full policy stack.
KMS Encryption Can Introduce Extra Permission Requirements
If the object uses KMS encryption, the signing principal may also need the relevant KMS permissions. Otherwise, a download or upload can fail even though the S3 permission itself looks correct.
This is especially common with uploads that require a specific KMS key or downloads from encrypted private buckets.
A Practical Debugging Checklist
Work through these checks in order:
- confirm the signer identity with
aws sts get-caller-identity - confirm bucket and key are correct
- confirm the bucket region
- confirm the signer has the required S3 action on that object
- check bucket policy for explicit denies
- if uploading, verify method and signed headers match the actual request
- if using KMS, verify key permissions too
That sequence usually finds the issue faster than rotating credentials or regenerating URLs repeatedly.
Common Pitfalls
- Assuming a presigned URL bypasses IAM or bucket policy checks.
- Signing against the wrong region.
- Using
put_objectpresigned URLs with mismatched headers at upload time. - Signing the wrong key or wrong bucket prefix.
- Forgetting extra KMS permissions when encrypted objects are involved.
Summary
- '
AccessDeniedfrom a presigned S3 URL usually means a policy, region, or request mismatch problem.' - The signer still needs real permission for the exact object operation being presigned.
- Download and upload URLs have different failure modes, especially around headers.
- Bucket policy and KMS policy can deny access even when IAM looks correct.
- Debug by checking identity, region, object path, request method, and policy layers in that order.

