Cloud

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 VpcConfig set? 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 Errors and Duration > 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.

Recommended

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 →
Never miss an article