Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 8 min read

Terraform Error: S3 Backend 'ResourceNotFoundException' — DynamoDB Lock Table Missing

Quick answer

Fix Terraform's S3 backend 'ResourceNotFoundException' error: create the DynamoDB state-lock table with a LockID key, fix dynamodb_table config, region mismatch, and IAM permissions.

  • #terraform
  • #iac
  • #troubleshooting
  • #errors
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Exact Error Message


│ Error: Error acquiring the state lock

│ Error message: operation error DynamoDB: PutItem, https response error
│ StatusCode: 400, RequestID: 7QJ..., ResourceNotFoundException:
│ Requested resource not found

│ Terraform acquires a state lock to protect the state from being written by
│ multiple users at the same time. Please resolve the issue above and try again.

What It Means

When the S3 backend is configured with dynamodb_table (or the newer use_lockfile alternative), Terraform writes a lock item to a DynamoDB table before it modifies state. ResourceNotFoundException: Requested resource not found means DynamoDB accepted the request but the named table does not exist in the account and region Terraform is targeting.

Unlike an AccessDenied, this is not a permission problem — AWS is telling you the table simply is not there. The usual cause is a table that was never created, was deleted, has a typo in its name, or lives in a different region than the backend block declares.

Common Causes

  • The DynamoDB lock table named in dynamodb_table was never created.
  • The table exists in a different region than the backend’s region.
  • The table name has a typo or differs from the one bootstrapped.
  • The table was deleted or recreated in another account.
  • The table was created without a partition key named LockID, so it is unusable for locking (this often surfaces as a validation error rather than not-found, but is worth ruling out).

Diagnostic Commands

Confirm the identity and region Terraform is using:

aws sts get-caller-identity
echo "$AWS_REGION"

Read the exact table name and region from your backend configuration:

grep -A8 'backend "s3"' *.tf

Check whether the table actually exists in that region:

aws dynamodb describe-table \
  --table-name terraform-locks \
  --region us-east-1 \
  --query 'Table.{Name:TableName,Key:KeySchema,Status:TableStatus}'

List all tables in the region to catch a name mismatch:

aws dynamodb list-tables --region us-east-1

Step-by-Step Resolution

  1. Confirm the table name and region in the backend block match a real table:
terraform {
  backend "s3" {
    bucket         = "my-tf-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
  }
}
  1. If the table does not exist, create it. The partition key must be a string attribute named LockID:
aws dynamodb create-table \
  --table-name terraform-locks \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --region us-east-1
  1. Wait for the table to become active before retrying:
aws dynamodb wait table-exists --table-name terraform-locks --region us-east-1
  1. Prefer managing the lock table in code so it is reproducible (bootstrap it in a separate root module, not the one that uses it):
resource "aws_dynamodb_table" "tf_lock" {
  name         = "terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}
  1. Ensure the executing identity can use the table (GetItem, PutItem, DeleteItem):
{
  "Effect": "Allow",
  "Action": ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:DeleteItem"],
  "Resource": "arn:aws:dynamodb:us-east-1:111122223333:table/terraform-locks"
}
  1. Re-initialize and run a plan; the lock should now acquire cleanly:
terraform init -reconfigure
terraform plan

If you would rather move to the newer native S3 lock (use_lockfile = true, no DynamoDB), the Terraform backend migration prompts in the prompt library can generate the config change and the safe cutover steps.

Prevention

  • Bootstrap the state bucket and lock table together in a dedicated, version-controlled root module.
  • Always use LockID (string) as the partition key; any other key makes the table unusable for locking.
  • Pin region and dynamodb_table explicitly so a copied backend cannot point at a nonexistent table.
  • Protect the lock table with prevent_destroy so it is not accidentally removed.
  • Prefer PAY_PER_REQUEST billing so an unprovisioned table never throttles lock operations.
  • Consider the native use_lockfile S3 lock (Terraform 1.10+) to remove the separate DynamoDB dependency entirely.
  • AccessDenied on DynamoDB — the table exists but the identity lacks PutItem/DeleteItem.
  • ConditionalCheckFailedException / Error acquiring the state lock ... ID: ... — a lock is already held, not a missing table.
  • NoSuchBucket — the S3 state bucket is missing, a separate backend problem.
  • ValidationException: One or more parameter values were invalid — the table key schema is wrong (not LockID).

Frequently Asked Questions

Does the lock table need a specific key name? Yes — the partition key must be a string attribute named exactly LockID; Terraform writes and reads items by that key, so any other name breaks locking.

Why does it work in one region but not another? DynamoDB tables are regional, so a table in us-east-1 is invisible to a backend configured for eu-west-1; align region with where the table lives.

Can I skip DynamoDB entirely? On Terraform 1.10 and later you can set use_lockfile = true on the S3 backend to store the lock as an object in the same bucket, removing the DynamoDB table requirement.

Should I create the lock table with Terraform? Yes, but in a separate bootstrap module that does not itself depend on the lock, so you avoid a chicken-and-egg problem. For more state-backend fixes, see the Terraform guides.

Free download · 368-page PDF

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.