AWS Error: '403 when calling the HeadObject operation: Forbidden' — Cause, Fix, and Troubleshooting Guide
Fix the S3 '(403) when calling the HeadObject operation: Forbidden' from aws s3 cp/sync: missing GetObject, KMS decrypt, or a bucket-policy deny.
- #aws
- #cloud
- #troubleshooting
- #errors
- #s3
Stuck on this AWS with AI error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
aws s3 cp/sync issue a HeadObject request before downloading an object. When your principal is allowed to reach the bucket but not to read that specific object, S3 answers the head request with HTTP 403 Forbidden. Because HeadObject returns no error body, the CLI surfaces a bare 403 ... Forbidden with no detail — which makes this one of the more confusing S3 errors to triage.
You will see it from the CLI:
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden
It occurs when the object is readable in principle but blocked by a missing s3:GetObject, a denied KMS key, a bucket-policy Deny, cross-account object ownership, or (deceptively) a wrong key with no ListBucket permission to distinguish 404 from 403.
Symptoms
aws s3 cp s3://bucket/key .oraws s3 syncfails with(403) ... HeadObject ... Forbidden.aws s3 ls s3://bucket/may succeed while downloading a specific object fails (or vice versa).- The same object downloads fine for the account root or the bucket owner but not for your role.
- It appears only for KMS-encrypted objects, or only for objects uploaded by another account.
aws s3 cp s3://app-artifacts-REDACTED/builds/app.tar.gz .
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden
Common Root Causes
1. Missing s3:GetObject on the object
Your identity/bucket policy grants ListBucket but not GetObject for that key/prefix.
2. No permission on the KMS key (SSE-KMS)
The object is encrypted with a KMS key your principal can’t kms:Decrypt, so the read is denied even with GetObject.
3. A bucket-policy or SCP explicit Deny
A Deny (e.g. requiring a VPC endpoint, TLS, or specific principal) overrides your allow.
4. Cross-account object ownership / ACLs
The object was uploaded by another account and, without Bucket Owner Enforced / correct ACLs, the bucket owner (you) can’t read it.
5. A wrong key masquerading as 403
Without s3:ListBucket, S3 returns 403 instead of 404 for a non-existent key — so a typo’d path looks like a permissions problem.
How to diagnose
Step 1: Confirm your identity and target
aws sts get-caller-identity --query Arn --output text
aws s3api head-object --bucket app-artifacts-REDACTED --key builds/app.tar.gz
The raw head-object returns the same 403; if ListBucket is present, list to confirm the key exists:
aws s3api list-objects-v2 --bucket app-artifacts-REDACTED --prefix builds/app.tar.gz \
--query 'Contents[].Key' --output text
Empty output = wrong key (a 404 wearing a 403 mask); a hit = genuine permission issue.
Step 2: Check the bucket policy for denies and KMS
aws s3api get-bucket-policy --bucket app-artifacts-REDACTED --query Policy --output text | python3 -m json.tool
aws s3api head-object --bucket app-artifacts-REDACTED --key builds/app.tar.gz \
--query '[ServerSideEncryption,SSEKMSKeyId]' 2>/dev/null
Step 3: Simulate the object read
aws iam simulate-principal-policy \
--policy-source-arn "$(aws sts get-caller-identity --query Arn --output text)" \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::app-artifacts-REDACTED/builds/app.tar.gz \
--query 'EvaluationResults[].EvalDecision' --output text
Fixes
Grant s3:GetObject on the prefix
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::app-artifacts-REDACTED/builds/*"
}
Grant KMS decrypt for SSE-KMS objects
Add kms:Decrypt for the object’s key to your policy, and ensure the KMS key policy trusts your principal:
{ "Effect": "Allow", "Action": "kms:Decrypt", "Resource": "arn:aws:kms:us-east-1:111122223333:key/REDACTED" }
Remove or satisfy the bucket-policy Deny
If a Deny requires a VPC endpoint or TLS, access the bucket via the required path (through the endpoint / over HTTPS) rather than editing the guardrail away.
Fix cross-account ownership
Enable Bucket Owner Enforced (Object Ownership) so uploads land owned by the bucket owner, or have the uploader grant bucket-owner-full-control.
What to watch out for
HeadObjectreturns no error detail — always reproduce withs3api head-objectand cross-check withlist-objects-v2to separate 403 from a hidden 404.- Add
s3:ListBucketso missing keys return an honest 404 instead of a misleading 403. - SSE-KMS needs both
s3:GetObjectandkms:Decrypt, and the KMS key policy must also allow your principal. - An explicit
Denyin the bucket policy or an SCP wins over anyAllow— check for denies before adding more allows.
Related
- AWS Error: ‘AccessDenied … not authorized to perform’ — the general IAM authorization pattern behind the 403.
- AWS Error: KMS ‘disabled key’ / access denied — the KMS side of an SSE-KMS read failure.
- AWS Error: ‘NoSuchBucket’ — when the bucket itself, not the object, can’t be found.
Fixed it? Get 500 AWS with AI & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.