How to fix Error loading state: AccessDenied: Access Denied status code: 403 in Terraform

TerraformINTERMEDIATEHIGHUpdated Dec 30, 2025ยท 148 views

This error occurs when Terraform cannot access your S3 bucket used for remote state storage. It typically results from missing IAM permissions, incorrect AWS credentials, or misconfigured backend settings.

Quick fix

Fixes Error loading state: AccessDenied: Access Denied status code: 403

aws sts get-caller-identity
  1. Verify AWS credentials are configured correctly
  2. Verify S3 bucket exists and is accessible
  3. Verify IAM permissions for S3 backend operations
  4. Clear local Terraform state and reinitialize
terraform โ€” error
aws sts get-caller-identity
Error loading state: AccessDenied: Access Denied status code: 403

What this error means

Terraform stores infrastructure state in an S3 bucket to enable collaboration and maintain state consistency. When Terraform tries to read or write state, AWS returns a 403 Forbidden error if the credentials used lack required permissions or the bucket configuration denies access. This is a permissions issue between your AWS identity (user or role) and the S3 bucket.

Typical symptoms

  • terraform init, plan, or apply fails with "Error loading state: AccessDenied: Access Denied status code: 403"
  • Error message mentions "Failed to get existing workspaces: Unable to list objects in S3 bucket"
  • Terraform works locally but fails in CI/CD pipelines with S3 backend
  • Switching Terraform backend configurations triggers access denied errors
  • Multiple AWS accounts: getting errors when trying to use S3 bucket in a different account

Common causes

  1. IAM user or role lacks required S3 permissions (s3:GetObject, s3:PutObject, s3:ListBucket)
  2. AWS credentials in Terraform backend configuration point to wrong user or role
  3. Local Terraform state (.terraform/ directory) contains references to a deleted or inaccessible S3 bucket
  4. S3 bucket policy explicitly denies access to the IAM principal
  5. AWS CLI profile mismatch: backend uses default profile but credentials are configured for a different profile
  6. Multiple AWS account setup: trying to access S3 backend from an account that does not have cross-account permissions
  7. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) contain outdated or incorrect credentials
  8. Assume role chicken-and-egg problem: assume_role is configured but Terraform cannot assume the role before checking state

How to fix "Error loading state: AccessDenied: Access Denied status code: 403"

1Verify AWS credentials are configured correctly

Ensure your AWS credentials are set up and accessible to Terraform. Check via:

bash
aws sts get-caller-identity

This command returns the AWS account ID, ARN, and username. Verify it matches the account and user you expect. If this fails, configure credentials using:

bash
aws configure

Or set environment variables:

bash
export AWS_ACCESS_KEY_ID=your_access_key
export AWS_SECRET_ACCESS_KEY=your_secret_key
export AWS_DEFAULT_REGION=us-east-1
2Verify S3 bucket exists and is accessible

Use AWS CLI to directly test access to the S3 bucket:

bash
aws s3 ls s3://your-bucket-name/

If this command fails with "NoSuchBucket" or an access denied error, the bucket either does not exist or your credentials cannot access it. If it succeeds, proceed to check IAM permissions. Double-check the bucket name in your Terraform configuration (terraform/backend.tf) for typos.

3Verify IAM permissions for S3 backend operations

Your IAM user or role needs these minimum permissions:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:PutObjectAcl",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetBucketVersioning"
      ],
      "Resource": "arn:aws:s3:::your-bucket-name"
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:DescribeTable",
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:DeleteItem"
      ],
      "Resource": "arn:aws:dynamodb:*:*:table/your-lock-table"
    }
  ]
}

If using DynamoDB for state locking, include the DynamoDB permissions. Check your IAM policy in the AWS Console under "Users" or "Roles" to ensure these actions are allowed.

4Clear local Terraform state and reinitialize

Delete the local .terraform directory and terraform.tfstate files to remove any stale references:

bash
rm -rf .terraform terraform.tfstate terraform.tfstate.backup

Then reinitialize:

bash
terraform init

This forces Terraform to fetch the latest state from the S3 backend fresh. This resolves issues where local state refers to deleted buckets or old backend configurations.

5Enable Terraform debug logging

Get more details about what Terraform is doing when accessing S3:

bash
export TF_LOG=DEBUG
terraform init 2>&1 | tee terraform-debug.log

Search the log for lines starting with "ERROR" or containing "Access Denied" to see the exact AWS API call failing. This helps identify whether the issue is s3:ListBucket, s3:GetObject, or a different permission.

6Check for bucket policy blocking access

The S3 bucket itself may have a policy explicitly denying access. View and modify the bucket policy via:

bash
aws s3api get-bucket-policy --bucket your-bucket-name

Look for any Deny statements targeting your AWS account or IAM principal. If found, remove or modify the policy to allow your IAM user/role access. To set a permissive policy:

bash
aws s3api put-bucket-policy --bucket your-bucket-name --policy file://policy.json

where policy.json contains your desired policy.

7Verify backend configuration in terraform/backend.tf

Check your backend configuration for typos and correct values:

hcl
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    # Optional: enable state locking
    dynamodb_table = "terraform-locks"
  }
}

Common mistakes:
- Typo in bucket name
- Region does not match bucket location
- Missing or incorrect key path
- Using assume_role without proper credential chain setup

8For CI/CD: use explicit credentials in backend configuration

In CI/CD environments, explicitly provide credentials in the backend block instead of relying on environment discovery:

hcl
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    access_key     = var.aws_access_key
    secret_key     = var.aws_secret_key
  }
}

Or pass credentials via environment variables during terraform init:

bash
export AWS_ACCESS_KEY_ID=$CI_AWS_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=$CI_AWS_SECRET_KEY
terraform init

This prevents profile mismatches between local and CI environments.

Advanced notes

For multi-account setups, ensure the S3 bucket account has a bucket policy allowing cross-account access from your Terraform execution role. Example policy for cross-account access:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::OTHER_ACCOUNT_ID:root"
      },
      "Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
      "Resource": ["arn:aws:s3:::bucket-name", "arn:aws:s3:::bucket-name/*"]
    }
  ]
}

When using assume_role with S3 backend, ensure the assume_role parameters are specified at the provider level, not in the backend block. The backend does not support role assumptionโ€”it uses the credentials from the AWS provider or environment.

For S3 buckets with KMS encryption, additional kms:Decrypt and kms:GenerateDataKey permissions are required on the KMS key.

Always use least-privilege IAM policies: do not grant S3FullAccess or AdministratorAccess to Terraform users in production.

Frequently asked questions

What does "Error loading state: AccessDenied: Access Denied status code: 403" mean?
Terraform stores infrastructure state in an S3 bucket to enable collaboration and maintain state consistency. When Terraform tries to read or write state, AWS returns a 403 Forbidden error if the credentials used lack required permissions or the bucket configuration denies access. This is a permissions issue between your AWS identity (user or role) and the S3 bucket.
How do I fix "Error loading state: AccessDenied: Access Denied status code: 403"?
This error occurs when Terraform cannot access your S3 bucket used for remote state storage. It typically results from missing IAM permissions, incorrect AWS credentials, or misconfigured backend settings.
What causes "Error loading state: AccessDenied: Access Denied status code: 403"?
IAM user or role lacks required S3 permissions (s3:GetObject, s3:PutObject, s3:ListBucket) AWS credentials in Terraform backend configuration point to wrong user or role Local Terraform state (.terraform/ directory) contains references to a deleted or inaccessible S3 bucket
Is the "Error loading state: AccessDenied: Access Denied status code: 403" error serious?
Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading