Terraform Error: S3 Backend 'ResourceNotFoundException' — DynamoDB Lock Table Missing
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
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_tablewas 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
- 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"
}
}
- 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
- Wait for the table to become active before retrying:
aws dynamodb wait table-exists --table-name terraform-locks --region us-east-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"
}
}
- 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"
}
- 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
regionanddynamodb_tableexplicitly so a copied backend cannot point at a nonexistent table. - Protect the lock table with
prevent_destroyso it is not accidentally removed. - Prefer
PAY_PER_REQUESTbilling so an unprovisioned table never throttles lock operations. - Consider the native
use_lockfileS3 lock (Terraform 1.10+) to remove the separate DynamoDB dependency entirely.
Related Errors
AccessDeniedon DynamoDB — the table exists but the identity lacksPutItem/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 (notLockID).
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.
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.