All Articles

Terraform S3 Access Denied — How to Fix It (2026)

Getting AccessDenied when Terraform tries to read or write your S3 backend? Here's every cause and the exact fix.

DevOpsBoysApr 8, 20262 min read
Share:Tweet

Terraform hits S3 Access Denied most often during init (reading state) or apply (writing state). The error looks like this:

Error: Failed to get existing workspaces: S3 bucket does not have versioning enabled
Error: AccessDenied: Access Denied
        status code: 403

Here's how to find the cause and fix it.


The 5 Most Common Causes

  1. IAM user/role doesn't have S3 permissions
  2. S3 bucket policy blocks the IAM principal
  3. Wrong region configured
  4. Bucket doesn't exist or typo in bucket name
  5. KMS encryption — IAM has S3 access but not KMS decrypt

Fix 1: Check IAM Permissions

Your Terraform runner needs these S3 permissions at minimum for remote state:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket",
        "s3:GetBucketVersioning"
      ],
      "Resource": [
        "arn:aws:s3:::your-terraform-state-bucket",
        "arn:aws:s3:::your-terraform-state-bucket/*"
      ]
    }
  ]
}

Test quickly:

bash
aws s3 ls s3://your-terraform-state-bucket
aws sts get-caller-identity  # confirm which IAM entity is running

Fix 2: Check Bucket Policy

Even with the right IAM permissions, a restrictive bucket policy can block access.

bash
aws s3api get-bucket-policy --bucket your-terraform-state-bucket

If the bucket policy has an explicit Deny or doesn't have an Allow for your principal — that's your issue.

Add your IAM role/user to the bucket policy:

json
{
  "Principal": {
    "AWS": "arn:aws:iam::123456789:role/terraform-runner-role"
  },
  "Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
  "Effect": "Allow",
  "Resource": [
    "arn:aws:s3:::your-terraform-state-bucket",
    "arn:aws:s3:::your-terraform-state-bucket/*"
  ]
}

Fix 3: KMS Encryption

If your S3 bucket uses KMS encryption (SSE-KMS), the IAM role also needs KMS permissions:

json
{
  "Effect": "Allow",
  "Action": [
    "kms:GenerateDataKey",
    "kms:Decrypt"
  ],
  "Resource": "arn:aws:kms:us-east-1:123456789:key/your-kms-key-id"
}

Check if the bucket uses KMS:

bash
aws s3api get-bucket-encryption --bucket your-terraform-state-bucket

Fix 4: DynamoDB Lock Table

If using DynamoDB for state locking, you also need these permissions:

json
{
  "Effect": "Allow",
  "Action": [
    "dynamodb:GetItem",
    "dynamodb:PutItem",
    "dynamodb:DeleteItem"
  ],
  "Resource": "arn:aws:dynamodb:us-east-1:123456789:table/terraform-state-lock"
}

Fix 5: Wrong Region

hcl
terraform {
  backend "s3" {
    bucket = "your-terraform-state-bucket"
    key    = "prod/terraform.tfstate"
    region = "us-east-1"  # must match bucket region
  }
}

If the bucket is in ap-south-1 but backend says us-east-1 — you get Access Denied.

Check bucket region:

bash
aws s3api get-bucket-location --bucket your-terraform-state-bucket

Quick Debug Checklist

bash
# Who am I?
aws sts get-caller-identity
 
# Can I list the bucket?
aws s3 ls s3://your-bucket
 
# Can I read a specific key?
aws s3 cp s3://your-bucket/path/terraform.tfstate /tmp/test.tfstate
 
# Check bucket encryption
aws s3api get-bucket-encryption --bucket your-bucket

Work through this list top to bottom — 95% of S3 Access Denied errors are fixed by step 2.


Resources

Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments