Skip to content
AWS S3 📅 2026-02-02

Fixing Access Denied (HTTP 403 Forbidden) Errors in Amazon S3

Encountering an Access Denied (HTTP 403 Forbidden) error when interacting with Amazon S3 buckets can halt deployments and disrupt critical workflows. As SREs, diagnosing and rectifying these authorization failures swiftly is paramount. This guide provides production-ready CLI commands and strategies to identify the root cause and implement a permanent fix, minimizing latency in your S3 operations.

🚨 Symptoms & Diagnosis

When an S3 request fails with an Access Denied error, the signature will typically manifest in logs or API responses as one of the following:

AccessDenied: Access Denied
HTTP 403 Forbidden
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>ABC123XYZ</RequestId><HostId>hostid123</HostId></Error>
A client error (AccessDenied) occurred when calling the PutObject operation: Access Denied
Policy type that denied access: BUCKET_POLICY | Reason: Missing required s3:PutObject permission

Root Cause: S3 Access Denied errors are fundamentally authorization failures, stemming from either an explicit deny statement within an IAM policy, bucket policy, or Service Control Policy (SCP), or an implicit deny due to the absence of an explicit Allow statement for the attempted S3 action and resource. Block Public Access settings or incorrect/expired credentials can also lead to this state.


🛠️ Solutions

Below are actionable solutions, ranging from quick diagnostic steps to permanent policy adjustments.

Quick Fix: Verify & Test Current Credentials

This initial step ensures your current AWS CLI configuration and active credentials possess at least basic S3 interaction capabilities. This helps quickly rule out simple authentication issues before diving into complex policy analysis.

Immediate Mitigation: Verify & Test Current Credentials

Perform an immediate check of your active AWS credentials and attempt a basic S3 list operation. The --debug flag can provide enhanced context for 403 errors.

  1. Confirm AWS Credentials: Validate the IAM entity associated with your current CLI session.
    aws sts get-caller-identity
    
  2. Test Bucket List Access with Debug Output: Attempt to list the bucket content. The --debug flag provides verbose output that often includes the specific policy type denying access.
    echo 'Test S3 access:'
    aws s3 ls s3://your-bucket-name --debug | grep -i 'accessdenied\|403'
    
    If a 403 error occurs, carefully review the debug output for any Policy type that denied access messages, which are crucial clues.

Diagnose Policy Denials (CLI Policy Simulator)

For more complex scenarios, leveraging the IAM Policy Simulator and analyzing CloudTrail logs provides deep insights into why a specific action is being denied. This is critical for identifying explicit denies or missing allows.

Best Practice Fix: Diagnose Policy Denials (CLI Policy Simulator)

Proactively simulate S3 actions against your IAM principal and examine CloudTrail for detailed authorization events to pinpoint the exact policy causing the denial.

  1. Get Current Identity ARN: Retrieve the Amazon Resource Name (ARN) of the principal (user/role) currently making the request.
    IDENTITY_ARN=$(aws sts get-caller-identity --query 'Arn' --output text)
    echo "Current Identity ARN: $IDENTITY_ARN"
    
  2. Simulate S3 Actions with Policy Simulator: Use the IAM policy simulator to evaluate whether a specific S3 action (e.g., s3:PutObject) would be allowed or denied for your principal on the target resource.
    aws iam simulate-principal-policy \
      --policy-source-arn $IDENTITY_ARN \
      --action-names s3:PutObject \
      --resource-arn arn:aws:s3:::your-bucket-name/your-key
    
    Analyze the EvaluationResults output to see if the action is allowed or denied and which policy led to that decision.
  3. Parse CloudTrail for 403 Context: CloudTrail logs every API call. Filtering for AccessDenied events can reveal the exact request and the policies evaluated.
    aws logs filter-log-events \
      --log-group-name CloudTrail/DefaultLogGroup \
      --filter-pattern 'AccessDenied' \
      --query 'events[*].message' \
      --output text
    
    Review the event details for errorCode and errorMessage to understand the denial context.

Permanent Fix: Update IAM Policy with Required Permissions

Often, an Access Denied error is due to an implicit deny – the IAM policy attached to the principal simply doesn't grant the necessary S3 permissions. This solution addresses such cases by explicitly adding the required permissions.

Best Practice Fix: Update IAM Policy with Required Permissions

Provision an IAM policy with the minimum necessary S3 actions and attach it to the problematic IAM user or role, ensuring explicit Allow statements.

  1. Create Policy JSON: Define a JSON policy document that grants the required S3 permissions (e.g., GetObject, PutObject, ListBucket, DeleteObject). Adjust actions and resources (arn:aws:s3:::your-bucket-name/*) as needed for your specific use case.
    cat > s3-full-access.json <<EOF
    {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket", "s3:DeleteObject"],
        "Resource": ["arn:aws:s3:::your-bucket-name", "arn:aws:s3:::your-bucket-name/*"]
      }]
    }
    EOF
    
  2. Create and Attach IAM Policy: Create the policy and then attach it to the IAM user or role. Replace your-iam-user with the target principal's name.
    aws iam create-policy --policy-name S3FullAccessPolicy --policy-document file://s3-full-access.json
    # Note: Replace ACCOUNT with your AWS Account ID
    aws iam attach-user-policy --user-name your-iam-user --policy-arn arn:aws:iam::ACCOUNT:policy/S3FullAccessPolicy
    # If attaching to a role:
    # aws iam attach-role-policy --role-name your-iam-role --policy-arn arn:aws:iam::ACCOUNT:policy/S3FullAccessPolicy
    
  3. Review and Remove Explicit Deny Statements: Thoroughly review all IAM policies (including inline policies) attached to the principal for any explicit Deny statements that might override your new Allow policy. Explicit Deny always takes precedence.
  4. Verify SCP and Permissions Boundary: Ensure that no Service Control Policies (SCPs) at the AWS Organization level or Permissions Boundaries attached to the IAM entity are implicitly or explicitly blocking the required S3 actions.

Permanent Fix: Fix Bucket Policy & Disable Block Public Access

Bucket policies provide resource-based permissions, controlling who can access the bucket and what actions they can perform. AWS S3 Block Public Access settings can override even explicit Allow statements in bucket policies for public access scenarios.

Data Exposure Warning

Modifying bucket policies or disabling Block Public Access can inadvertently expose your S3 bucket and its contents to the public internet. Exercise extreme caution and only proceed if public access is explicitly required and understood for your use case. Always adhere to the principle of least privilege.

Best Practice Fix: Fix Bucket Policy & Disable Block Public Access

Carefully construct or modify the S3 bucket policy to grant necessary resource-level permissions and, if absolutely required for public access, disable the relevant Block Public Access settings.

  1. Get Current Bucket Policy: Inspect the existing bucket policy to understand its current permissions configuration.
    aws s3api get-bucket-policy --bucket your-bucket-name
    
  2. Update Bucket Policy: Create a new policy JSON or modify the existing one to allow the necessary principal(s) and actions. The example below grants s3:* actions to all principals (*) – use with extreme caution and restrict as much as possible for production.
    cat > bucket-policy.json <<EOF
    {
      "Version": "2012-10-17",
      "Statement": [{
        "Sid": "AllowAll",
        "Effect": "Allow",
        "Principal": "*",
        "Action": "s3:*",
        "Resource": [
          "arn:aws:s3:::your-bucket-name",
          "arn:aws:s3:::your-bucket-name/*"
        ]
      }]
    }
    EOF
    aws s3api put-bucket-policy --bucket your-bucket-name --policy file://bucket-policy.json
    
  3. Disable Block Public Access (If Public Access is Required): If the intention is for the bucket or specific objects to be publicly accessible, you may need to adjust the Block Public Access settings. This should only be done if public access is a strict requirement, and you fully understand the security implications.
    aws s3api put-public-access-block \
      --bucket your-bucket-name \
      --public-access-block-configuration "BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false"
    
    Note that this command disables all public access blocks. You might only need to disable specific ones depending on your exact requirements (e.g., BlockPublicPolicy if you intend to have a public bucket policy).

🧩 Technical Context (Visualized)

The S3 Authorization Subsystem is a multi-layered evaluation process involving various policy types. When an S3 request is made, it traverses these layers, and a denial can occur at any stage if an explicit Deny is encountered or if no Allow is found. This complex interplay includes IAM Policies, Bucket Policies, Service Control Policies (SCPs), Permissions Boundaries, and Block Public Access settings.

graph TD
    A[S3 Request from Principal (User/Role)] --> B{1. AWS Organizations SCPs?};
    B -- Deny --> E[Access Denied (SCP Explicit Deny)];
    B -- Allow/No Match --> C{2. IAM Identity-based Policies (User/Role, Permissions Boundary)?};
    C -- Deny --> F[Access Denied (IAM/PB Explicit Deny)];
    C -- Allow/No Match --> D{3. S3 Resource-based Policies (Bucket Policy/ACL)?};
    D -- Deny --> G[Access Denied (Bucket Policy/ACL Explicit Deny)];
    D -- Allow/No Match --> H{4. S3 Block Public Access Settings?};
    H -- Blocked --> I[Access Denied (Block Public Access)];
    H -- Not Blocked --> J[Access Granted];
    C -- No Allow --> K[Access Denied (IAM/PB Implicit Deny)];
    D -- No Allow --> L[Access Denied (Bucket Policy Implicit Deny)];

✅ Verification

After implementing any changes, always verify that the issue is resolved and that the intended S3 operations can now complete successfully.

  1. List Bucket Contents: Confirm that you can now list objects in the bucket.
    aws s3 ls s3://your-bucket-name/
    
  2. Test Object Upload (Dry Run): Attempt to upload a dummy file using --dryrun to test permissions without actually writing data. Create a dummy file first: echo "test content" > /tmp/testfile.txt.
    aws s3 cp /tmp/testfile.txt s3://your-bucket-name/ --dryrun
    
  3. Head Object Check (Verify Read Access): If you expect to read an existing object, use head-object.
    aws s3api head-object --bucket your-bucket-name --key testfile.txt | grep -i 'AccessDenied\|403' || echo 'SUCCESS: Object accessible'
    
    For a full upload/download test:
    echo "This is a test file." > /tmp/temp_s3_test.txt
    aws s3 cp /tmp/temp_s3_test.txt s3://your-bucket-name/testfile.txt
    aws s3 rm s3://your-bucket-name/testfile.txt
    rm /tmp/temp_s3_test.txt
    

📦 Prerequisites

To execute the commands provided in this article, ensure the following prerequisites are met:

  • AWS CLI v2.15+: Verify your AWS CLI version using aws --version.
  • IAM Admin or Appropriate Credentials: You must have sufficient IAM permissions to retrieve credentials, list S3 buckets, view/modify IAM policies, and view/modify S3 bucket policies/public access blocks.
  • jq (Optional but Recommended): For parsing JSON output from CLI commands, jq is highly recommended. Install it via your package manager (e.g., sudo apt-get install jq on Debian/Ubuntu, sudo yum install jq on RHEL/CentOS).