SadServers
  • Scenarios
  • Labs
    All Labs Linux & Bash Web Servers Databases Data Processing Docker Kubernetes CI/CD Infrastructure as Code Tooling / Applications
  • Dashboard
  • Solutions
    For Individuals For Businesses
  • Ranking
  • Newsletter
  • Documentation
    FAQ Support Pro Accounts Pro+ Accounts Business Accounts Gift API CLI/TUI Privacy Troubleshooting Interviews
  • Blog
  • Pricing
  • Gift
    Gift Purchase Gift Redeem
  • About
Log In - Sign Up
  1. Labs
  2. Terraform
  3. Troubleshooting

Guide

Concepts and learning path

Troubleshooting

Failure modes and fixes

Cheatsheet

Commands to keep handy

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.log

2. State and identity

terraform state list terraform state show 'aws_instance.web' terraform workspace show

3. Verbose provider logs

export TF_LOG=DEBUG export TF_LOG_PATH=./tf-debug.log terraform plan
Cheatsheet →
SadServersSadServers

Real-world Linux and DevOps scenarios for hands-on learning and technical assessment.

Uptime Robot ratio (30 days)
Product
  • Scenarios
  • For Individuals
  • For Businesses
  • Pricing
Resources
  • FAQ
  • Blog
  • Newsletter
Company
  • About Us
  • Support
  • Privacy Policy
  • Terms of Service
  • Contact
Connect With Us
info@sadservers.com

Made in Canada 🇨🇦
Updated: 2026-06-13 16:06 UTC – 2d2950a