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
Stuck on this Terraform error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
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.
Fixed it? Get 500 Terraform & 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.
Stuck on this? Start guided troubleshooting
Open an interactive diagnostic session with this error already loaded. Work a step-by-step plan, record what each check returns, land on a root cause, and export a clean incident summary — no account needed to start.
Did this fix your issue?
Solved it a different way?
Share the fix that worked for you — reviewed, then published to help the next engineer.
That looks like it may contain a secret (key, token, password, or connection string). Please remove it — a note with a detected secret can’t be published.
Thanks — that helps. Published notes appear after a quick review.
Trending errors this week
The error guides other engineers are actually reading right now.
- 1mount: wrong fs type, bad option, bad superblock
- 2Docker 'failed to set up container networking': Fix the Bridge and IP Pool
- 3Docker 'failed to create shim task': How to Fix the containerd Runtime Error
- 4Transport endpoint is not connected
- 5modprobe: FATAL: Module not found
- 6mount: wrong fs type, bad option, bad superblock
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.