Why S3 says Access Denied (403), and how to find the cause

Updated · 5 min read

An error occurred (AccessDenied) when calling the GetObject operation: Access Denied

S3 says "Access Denied" for a lot of different problems. In a browser it's a short XML document with <Code>AccessDenied</Code>. Work down this list: the first four cover most cases.

1. Read the whole message

For requests inside one AWS account, or inside one AWS Organization, S3 now says why. The message ends with something like:

  • because no identity-based policy allows the s3:GetObject action: nothing gives you that permission. See step 4.
  • with an explicit deny in a resource-based policy: the bucket policy says no. See step 5.
  • with an explicit deny in a service control policy, or in a permissions boundary or session policy: the message includes that policy's ARN.

Requests from an account outside your organization only get a plain "Access Denied", so for those you have to check each step.

2. Check who you are

aws sts get-caller-identity

Compare the ARN with who you expected to be: a wrong --profile or leftover AWS_ACCESS_KEY_ID variables are a common cause. A presigned link carries the permissions of whoever signed it. A plain S3 URL opened in a browser carries none, so it only works for files that are public.

3. The file might not exist

This one is confusing. If you aren't allowed s3:ListBucket on the bucket, S3 answers a request for a file that doesn't exist with 403, not 404, so it doesn't reveal what's there. Check the exact name: keys are case-sensitive (Report.pdf and report.pdf are different files), and the folder path is part of the name.

aws s3 ls s3://my-bucket/reports/

4. Your own permissions

Listing is permission on the bucket; reading and writing files is permission on the objects in it, which is the bucket ARN plus /*. Mixing them up is the classic S3 mistake:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::my-bucket"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}

s3:GetObject on arn:aws:s3:::my-bucket, without the /*, matches no files at all. Paste your policy into the IAM policy explainer, which flags exactly this. Or ask IAM's simulator, which checks your own policies but not the bucket policy:

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::111122223333:user/alice \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::my-bucket/reports/2026-09.pdf

allowed means your side is fine; implicitDeny means nothing allows it.

5. The bucket policy

Open S3 → General purpose buckets → the bucket → Permissions → Bucket policy, or:

aws s3api get-bucket-policy --bucket my-bucket --query Policy --output text

Look for "Effect": "Deny". A Deny beats any Allow, and the usual ones require HTTPS (aws:SecureTransport), a particular VPC endpoint (aws:SourceVpce), certain IP addresses (aws:SourceIp) or encryption on upload. For access from another account, both sides must say yes: the bucket policy has to allow that account, and that account's own IAM policies have to allow it too.

If a bucket policy has locked everyone out, the account's root user can still sign in and delete it.

6. A KMS key

Files encrypted with a KMS key you manage (SSE-KMS) need more than S3 permissions: reading needs kms:Decrypt on that key, and uploading needs kms:GenerateDataKey, allowed both in your IAM policy and in the key's own key policy. Files encrypted with the AWS managed key aws/s3 can only be read from the account that owns that key, so other accounts get Access Denied whatever the bucket policy says. The bucket's default is under Properties → Default encryption; one file's is on its page under Server-side encryption settings:

aws s3api get-bucket-encryption --bucket my-bucket

The default, SSE-S3, needs no extra permissions.

7. Public access

A request with no credentials only works if the file is public, and Block Public Access, on for every new bucket since April 2023, overrides any policy that tries to make it so. It also makes saving a public bucket policy fail with Access Denied. See how to make a bucket or file public, or send a link that expires instead.

Less common causes

  • Files uploaded by another account to a bucket that still uses ACLs belong to that account, and the bucket policy's Allows don't cover them. Setting Object Ownership to Bucket owner enforced (the default for new buckets) fixes it.
  • An expired presigned link says Request has expired.
  • Requester Pays buckets need --request-payer requester on the command.
  • A VPC endpoint policy, when the request comes from inside a VPC through an S3 endpoint.
  • CloudFront in front of the bucket: see CloudFront 403 errors from S3.

Or in Cloud GUI

Cloud GUI's Files page lists every bucket in your account with its region, and lets you click through the folders to check that a file exists and what it's called exactly, with sizes and dates. The free checkup lists buckets that are public through their bucket policy, and buckets with Block Public Access off. The IAM policy explainer reads any IAM or bucket policy in plain English and flags the classic S3 mistakes; it runs in your browser and sends nothing anywhere. With editing turned on (Pro), Files can download, upload and delete too. One limit: files encrypted with a KMS key you manage can't be downloaded through Cloud GUI yet, because its editing role isn't allowed to use your keys, so S3 refuses for reason 6 above.

See your whole AWS account in one calm view

Cloud GUI shows your files and functions from every region at once, in plain English, with what's failing up front. It connects with a read-only role you create and can delete any time. Free for one AWS account.

More guides

Free tool
IAM policy explainer

Paste an IAM policy and read it in plain English, with the risky parts flagged. It runs in your browser; nothing is sent anywhere.