Terraform troubleshooting
Error acquiring the state lock
Another apply or crashed process holds the lock (DynamoDB, Consul,
etc.). Wait for the other run to finish. If the lock is stale after a crash,
use terraform force-unlock LOCK_ID only when sure no other apply is
running. Prevent parallel applies to the same state in CI with concurrency
limits.
Provider authentication failed
AWS: missing AWS_ACCESS_KEY_ID / role assumption or expired SSO
session — run aws sts get-caller-identity. GCP: application default
credentials or GOOGLE_APPLICATION_CREDENTIALS. Azure: az login
or service principal env vars. Kubernetes provider: kubeconfig context wrong or
expired cert. Match the identity CI uses (OIDC role) vs local CLI profile.
Version constraint errors
terraform version too old for required_version, or
provider versions incompatible. Run terraform init -upgrade after
updating constraints. Commit lock file after upgrade so teammates get the same
providers. Pin major versions (~> 5.0) to avoid surprise breaking
provider releases.
Resource already exists / import needed
Apply fails because cloud object exists outside Terraform state. Import with
terraform import ADDR ID, then run plan to align config with reality.
For many resources, generate import blocks (Terraform 1.5+) or use
terraform plan -generate-config-out=generated.tf where supported.
Unexpected destroy in plan
Renamed resource block without moved block or
terraform state mv — Terraform sees destroy + create. Changed
for_each key or count index reshuffles addresses.
Add moved { from = ... to = ... } (Terraform 1.1+) to preserve
state mapping. Always read destroy lines before applying.
Cycle: resource depends on itself
Circular dependency in implicit references — resource A needs B and B needs A.
Break the cycle with a data source, split into two applies, or restructure
modules. depends_on can help ordering but does not fix true cycles.
Drift — plan wants to change unchanged config
Someone edited resources in the console, or a default changed in the provider.
Review diff attribute by attribute. Use lifecycle { ignore_changes = [...] }
sparingly for attributes outside your control (e.g. default tags applied by
org policy). Refresh-only plan: terraform plan -refresh-only.
Apply timeout or API rate limit
Large applies hit cloud API throttling — split into smaller modules or use
-target for incremental recovery (not a daily workflow). Increase
provider timeouts in resource blocks. Re-run apply; Terraform resumes from state
for resources already created.
Debugging workflow
1. Reproduce plan
terraform init
terraform validate
terraform plan -no-color 2>&1 | tee plan.log2. State and identity
terraform state list
terraform state show 'aws_instance.web'
terraform workspace show3. Verbose provider logs
export TF_LOG=DEBUG
export TF_LOG_PATH=./tf-debug.log
terraform plan