How to use this catalog
These are the errors that everyone who works with Terraform for more than a month runs into. They are grouped by the text of the message. If yours looks similar, read the cause first, then the fix.
If your error is not in this list, turn on tf-log-debug and look for a RESPONSE with HTTP 4xx or 5xx. The cloud usually tells you what went wrong.
Cycle: A, B, C
Error: Cycle: aws_security_group.web, aws_security_group.app
Cause
Resource A references B, and B references A. The dependency graph has a loop, so Terraform cannot tell which one to create first.
This happens most often with security groups:
resource "aws_security_group" "web" { ingress {security_groups = [aws_security_group.app.id]
}
}
resource "aws_security_group" "app" { ingress {security_groups = [aws_security_group.web.id]
}
}
Fix
Pull the rules out into separate aws_security_group_rule resources (or
aws_vpc_security_group_ingress_rule in newer versions of the AWS
provider):
resource "aws_security_group" "web" {}resource "aws_security_group" "app" {}resource "aws_vpc_security_group_ingress_rule" "web_from_app" {security_group_id = aws_security_group.web.id
referenced_security_group_id = aws_security_group.app.id
}
resource "aws_vpc_security_group_ingress_rule" "app_from_web" {security_group_id = aws_security_group.app.id
referenced_security_group_id = aws_security_group.web.id
}
Now the security groups are created first (with no references), then the rules between them.
Check the graph with tf-graph. You can see the cycles by eye.
Value depends on resource attributes that cannot be determined until apply
Error: Invalid for_each argument
The "for_each" value depends on resource attributes that cannot be
determined until apply, so Terraform cannot predict how many instances
will be created.
Cause
You are trying to use for_each = { ... } with a value that depends on a
resource. At plan time Terraform does not know that value yet, because the
resource does not exist, so it cannot tell how many instances there will be
and cannot build the plan.
Example:
resource "aws_iam_user" "users" {for_each = toset([for r in aws_iam_role.roles : r.name])
# ^^^^^^^^ aws_iam_role.roles does not exist yet at plan time
name = each.key
}
Fix: three options
(a) Move the computation into terraform.tfvars. If the keys are known
ahead of time, set them statically:
variable "user_names" {type = set(string)
default = ["alice", "bob"]
}
resource "aws_iam_user" "users" {for_each = var.user_names
name = each.key
}
(b) A two-phase apply. First run terraform apply -target=aws_iam_role.roles
to create the roles, then a plain apply creates the users. This is a
workaround. You should not rely on it in CI/CD, but sometimes it is the only
way out of a bind.
(c) Split into two states. One project creates the roles and exports them
through an output. A second project reads them with terraform_remote_state
and creates the users.
Saved plan is stale
Error: Saved plan is stale
The given plan file can no longer be applied because the state was
changed by another operation after the plan was created.
Cause
You saved a plan with terraform plan -out=plan.bin, and then someone else
(or CI) ran an apply. The state changed, so your plan is out of date.
Fix
Run the plan again:
terraform plan -out=plan.bin
terraform apply plan.bin
This is a safeguard, not a bug. Terraform will not apply a stale plan, because doing so could lose data.
Error acquiring the state lock
Error: Error acquiring the state lock
Lock Info:
ID: abc-123-...
Path: s3://my-bucket/terraform.tfstate
Operation: OperationTypeApply
Who: alice@laptop
Created: 2026-05-20 14:00:00 +0000 UTC
Cause
Someone (or you, in another terminal) is already running an apply. The state is locked so that two parallel applies do not clobber each other.
Fix
- First, wait. An apply can take a while. The message shows the ID and the time, so you can judge how long ago it started.
- If you know the process is dead, run
terraform force-unlock <ID>. This is dangerous: if the process is actually still alive, you let a second apply through and corrupt the state. Force-unlock only when you are sure.
Provider produced inconsistent final plan
Error: Provider produced inconsistent final plan
When expanding the plan for aws_s3_bucket.demo to include new values,
the provider produced a value for attribute "X" that is inconsistent
with the previous plan.
Cause
The provider contradicts itself: at plan time it returned one value for the attribute, and at apply time it returned another. This is a provider bug, not your code.
Fix
- Update the provider to the latest version:
terraform init -upgrade. - If you are already on the latest, open an issue in the provider's repo (https://github.com/hashicorp/terraform-provider-aws/issues).
- A temporary workaround:
lifecycle.ignore_changes = [that_attribute]. This hides the problem, it does not cure it. Use it while you wait for a patch.
Reference to undeclared resource
Error: Reference to undeclared resource
A managed resource "aws_s3_bucket" "logs" has not been declared.
Cause
Your code references aws_s3_bucket.logs, but there is no such resource in
the HCL. Either the name has a typo, or you forgot to write the resource
itself.
Fix
Search the project:
grep -r 'aws_s3_bucket' .
There should be a resource "aws_s3_bucket" "logs" {...} declaration
somewhere. If it is missing, add it. If it is there, check the name, the
case, and that it lives in the right module.
Inconsistent dependency lock file
Error: Inconsistent dependency lock file
The following dependency selections recorded in the lock file are
inconsistent with the current configuration:
- provider registry.terraform.io/hashicorp/aws: required by this
configuration but no version is selected
Cause
The required_providers block in the HCL changed (a new provider or a new
version), but .terraform.lock.hcl still holds the old versions.
Fix
terraform init -upgrade
This re-reads the HCL, updates the lockfile, and downloads the versions you
need. Afterward, commit the new .terraform.lock.hcl (yes, it gets
committed).
Error: error configuring Terraform AWS Provider
Error: error configuring Terraform AWS Provider: no valid credential
sources for Terraform AWS Provider found.
Cause
The AWS provider did not find credentials in any of the usual places:
environment variables, ~/.aws/credentials, or the instance IAM role. See
aws-provider for the credentials chain.
Fix
Make sure one of these is set up:
# option 1: environment variables
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_DEFAULT_REGION=us-east-1
# option 2: shared file
aws configure # writes ~/.aws/credentials
# check that the credentials are visible
aws sts get-caller-identity
If aws sts get-caller-identity works, Terraform will find them too.
Diagnostic pitfalls
-
The error message is on top, the cause is below. Long Terraform errors consist of a header (
Error: ...) and a block withon FILE line N, in BLOCK:and a piece of code. What matters is the last line or two before the header. That is where the root cause sits. -
"Similar" errors can be different. A "Cycle" from one pair of resources is a different cycle from another pair, and you fix each one on its own. Do not try to solve "all the Cycles at once".
-
Do not comment out HCL to get around an error. If the error is on line 50 and you comment it out, it will reappear on line 80. Cure it, do not hide it.
-
The provider logs are more informative than the Terraform message.
TF_LOG_PROVIDER=DEBUGplus a grep for 'HTTP/1.1 4' often shows you the original AWS message, which is clearer.