Fix: AWS S3 upload works locally but fails from Lambda
S3 uploads succeed from your laptop but Lambda throws AccessDenied or times out. Here are the 5 real causes and the fix for each.
The exact same S3 upload code works from your laptop but fails inside AWS Lambda:
An error occurred (AccessDenied) when calling the PutObject operation
Or:
Task timed out after 3.00 seconds
Local IAM user has permissions, Lambda has an execution role with AmazonS3FullAccess, and you’ve triple-checked the bucket name. Yet Lambda still fails. Here are the five real causes and how to fix each.
Diagnose first
Add real logging before assuming:
import boto3, os, json
def handler(event, context):
print(f"Region: {os.environ.get('AWS_REGION')}")
print(f"Role ARN: {context.invoked_function_arn}")
print(f"Bucket: {os.environ.get('BUCKET_NAME')}")
s3 = boto3.client('s3')
try:
s3.put_object(Bucket=os.environ['BUCKET_NAME'], Key='test.txt', Body=b'hello')
return {'ok': True}
except Exception as e:
print(f"Failed: {type(e).__name__}: {e}")
raise
CloudWatch logs will now show the exact failure — 90% of debugging starts here.
Cause 1 — Lambda IAM role missing bucket permissions
AmazonS3FullAccess is broad, but Lambda role attachment can fail silently. Or someone attached a scoped inline policy that doesn’t cover the target bucket.
Symptom: AccessDenied on s3:PutObject.
Fix — verify the actual attached policies:
aws iam list-attached-role-policies \
--role-name <lambda-execution-role>
aws iam list-role-policies \
--role-name <lambda-execution-role>
Add a specific inline policy for the bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::my-bucket/*"
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::my-bucket"
}
]
}
Attach to the role and retry. Note: bucket-name and objects-in-bucket need SEPARATE resource entries — one is bucket-arn, the other is bucket-arn/*.
Cause 2 — Bucket policy denies Lambda role
Bucket owner set an explicit Deny that overrides Lambda’s Allow. Explicit Deny always wins in IAM evaluation.
Symptom: AccessDenied even after IAM role has full S3 permissions.
Diagnose:
aws s3api get-bucket-policy --bucket my-bucket
Look for "Effect": "Deny" statements. Common ones: block by IP range, block non-encrypted uploads, block outside the account.
Fix — either amend the bucket policy or add the Lambda role ARN to the allowlist:
{
"Sid": "AllowLambdaWrites",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/my-lambda-role"
},
"Action": ["s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}
Cause 3 — Cross-region call from Lambda to S3
If Lambda runs in us-east-1 and the bucket is in ap-south-1, the call still works, but each request crosses AWS regions — much higher latency and larger uploads can time out entirely. AWS also charges cross-region data transfer for the upload payload.
Symptom: Task timed out on large files; works for small ones.
Diagnose:
aws s3api get-bucket-location --bucket my-bucket
Compare to Lambda’s region (in the ARN or console).
Fix — one of:
- Move Lambda to the bucket’s region (cheapest, fastest)
- Use S3 Transfer Acceleration on the bucket (added cost)
- Explicitly set the region in the S3 client:
s3 = boto3.client('s3', region_name='ap-south-1')
For most workloads, co-locating Lambda and S3 in the same region is the right answer.
Cause 4 — Lambda in a VPC has no route to S3
If Lambda is attached to a VPC (for RDS access, for example) and the VPC has no NAT gateway or S3 VPC endpoint, Lambda can’t reach S3 at all.
Symptom: Task timed out with no useful error, or EndpointConnectionError.
Diagnose:
- Check Lambda config: is
VpcConfigset? If yes, this cause is likely. - Check the private subnet’s route table for a NAT gateway or S3 VPC endpoint route.
Fix — add an S3 gateway VPC endpoint (free):
aws ec2 create-vpc-endpoint \
--vpc-id vpc-xxxxx \
--service-name com.amazonaws.<region>.s3 \
--route-table-ids rtb-xxxxx
Gateway endpoints are free. They route S3 traffic within AWS’s network without going out through NAT. This solves the routing problem AND saves NAT egress costs.
Cause 5 — Lambda timeout too short for the upload
Default Lambda timeout is 3 seconds. Uploading a 50MB file over cold-start latency easily exceeds that.
Symptom: Task timed out after 3.00 seconds.
Fix — raise timeout:
aws lambda update-function-configuration \
--function-name my-function \
--timeout 60
Or in Terraform / SAM / CloudFormation, set timeout: 60 (or higher).
Also increase memory: more memory = more CPU = faster S3 uploads:
aws lambda update-function-configuration \
--function-name my-function \
--memory-size 1024
Lambda CPU scales linearly with memory. At 1769MB you get one full vCPU; at 128MB (default) you get roughly a fourteenth of that. Uploads are network-bound but CPU still matters for TLS handshake and payload encoding.
The universal Lambda-S3 debug flow
Every failing case, in order:
# 1. CloudWatch logs — read the actual error
aws logs tail /aws/lambda/<function> --follow
# 2. Verify Lambda role's S3 permissions
aws iam list-attached-role-policies --role-name <lambda-role>
# 3. Check bucket policy
aws s3api get-bucket-policy --bucket <bucket>
# 4. Check regions match
aws s3api get-bucket-location --bucket <bucket>
# 5. Check Lambda VPC config
aws lambda get-function-configuration --function-name <fn> | grep -i vpc
Ninety-five percent of Lambda-S3 failures resolve in steps 1-3.
Prevention
For new Lambda functions that touch S3:
- Explicit resource-level IAM policies — never
Resource: "*"in production; scope to the specific bucket - S3 gateway VPC endpoint on every VPC that has Lambda functions — free and prevents future headaches
- Timeout at 30s minimum — 3s default is a footgun for any real work
- Memory at 512MB minimum — matches typical throughput needs for S3
- CloudWatch alarms on
ErrorsandDuration > 90% of timeout - X-Ray tracing enabled — shows exactly where time is spent (S3, DNS, IAM assume)
Bottom line
Lambda-S3 failures almost always trace to one of five layers: IAM role, bucket policy, region mismatch, VPC networking, or timeout. CloudWatch logs plus the standard AWS CLI commands above pinpoint the layer in minutes. When Lambda is in a VPC, an S3 gateway endpoint is the single highest-leverage fix — free, no downtime, solves connectivity permanently.
DevOps YAML Pack
36 production-ready configs — Kubernetes, Docker Compose, GitHub Actions, Terraform, Helm, Ansible. Every file heavily commented. Copy, paste, ship.
Get the pack — ₹499 →