Getting 403 Forbidden when uploading to S3 with a signed URL
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
A 403 Forbidden on S3 presigned uploads almost always means request mismatch, policy rejection, or expiration, not random network failure. The fastest debugging method is to compare what the signer expected against what the client actually sent, including every header that participates in signing. Once that request contract is explicit, most 403 issues become straightforward to fix.
Validate the Presign Contract First
A presigned URL is a signed contract that includes method, path, expiration, and often headers. If upload request differs from signed values, S3 rejects it.
Minimum checks:
- HTTP method matches, usually
PUTfor direct object upload. - Bucket and object key are correct.
- URL not expired.
- Required headers match signed headers exactly.
Presign example in Python:
If you include ContentType during signing, client must send the same value.
Match Client Upload Request Exactly
Upload with matching method and headers.
Common mismatch examples:
- Signing with
image/pngbut sendingapplication/octet-stream. - Signing no custom header but client adds one that was expected to be signed in strict workflows.
- Using
POSTto aPUTURL.
Inspect client code and network traces, not only backend logs.
Check Expiration and Clock Drift
Presigned URLs are time limited. If client clocks are skewed or upload is delayed in queues, URL may expire before use.
Good practice:
- Keep expiration short but realistic for file size and network conditions.
- Generate URL near upload time.
- Log URL creation time and upload start time.
If retries happen, generate a new presigned URL instead of reusing stale ones.
Verify Region and Endpoint Consistency
Bucket region mismatch causes signature validation errors that may surface as 403.
Ensure these align:
- Signing client region.
- Bucket region.
- Request endpoint region.
Check bucket region quickly:
If your app signs in one region and uploads to another endpoint, signatures will fail.
Review Bucket Policy and ACL Conditions
Even valid signatures can be blocked by bucket policy conditions.
Common policy blockers:
- Required SSE headers not provided.
- Key prefix restrictions violated.
- Principal restrictions incompatible with presigned workflow.
- Public access settings conflicting with ACL parameters.
If policy requires encryption header, include it in both signing params and upload request headers.
Browser uploads add one more moving part: CORS. A bad CORS configuration usually appears as a browser blocked request rather than a true S3 authorization failure, but teams often debug the wrong layer first. Confirm whether the failure is an S3 XML error response or a browser side preflight problem before changing signing logic.
Special Case: Multipart Upload
Large files often use multipart upload, which requires a different signed flow. A single put_object presigned URL does not support multipart part upload semantics automatically.
For multipart:
- Create multipart upload.
- Presign each part upload URL.
- Upload parts.
- Complete multipart upload.
Mixing single PUT presign with multipart clients can yield repeated 403 errors.
Debugging Checklist
When you hit 403:
- Reproduce with
curlusing exact signed headers. - Check URL expiration time.
- Verify bucket region and endpoint.
- Compare signing headers and actual request headers.
- Review bucket policy conditions for key and encryption requirements.
This sequence identifies most failures quickly.
If possible, log the canonical request inputs at signing time in a redacted form. Seeing method, key, content type, region, and expiration in one place makes comparison with the client request much faster.
Common Pitfalls
- Signing one
Content-Typevalue but uploading with a different one. - Reusing expired presigned URLs in delayed upload workflows.
- Using incorrect region endpoint relative to signed region scope.
- Ignoring bucket policy constraints that override otherwise valid signatures.
- Attempting multipart upload with a single PUT presigned URL.
Summary
- A presigned URL is a strict request contract, not a generic permission token.
- Match method, path, headers, and expiration exactly.
- Verify region consistency between signer, bucket, and upload endpoint.
- Include required policy headers in both signing and upload request.
- Use a structured checklist to isolate 403 causes quickly.

