AWS S3
Signed URLs
Access Denied
Cloud Storage
Security

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:

json
1{
2  "Version": "2012-10-17",
3  "Statement": [
4    {
5      "Effect": "Allow",
6      "Action": "s3:GetObject",
7      "Resource": "arn:aws:s3:::my-bucket/private/*"
8    }
9  ]
10}

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.

python
1import boto3
2
3s3 = boto3.client("s3", region_name="us-east-1")
4
5url = s3.generate_presigned_url(
6    ClientMethod="get_object",
7    Params={
8        "Bucket": "my-bucket",
9        "Key": "private/report.pdf",
10    },
11    ExpiresIn=600,
12)
13
14print(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:

python
import boto3

s3 = boto3.client("s3", region_name="eu-west-1")

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.

python
1import boto3
2
3s3 = boto3.client("s3", region_name="us-east-1")
4
5url = s3.generate_presigned_url(
6    ClientMethod="put_object",
7    Params={
8        "Bucket": "my-bucket",
9        "Key": "uploads/photo.jpg",
10        "ContentType": "image/jpeg",
11    },
12    ExpiresIn=300,
13)
14
15print(url)

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_object presigned 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

  • 'AccessDenied from 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.

Course illustration
Course illustration

All Rights Reserved.