Skip to main content
RunBook Academy

TerraformXVIII · Troubleshooting and RecoveryProduction Terraform

Troubleshooting State Issues

Intermediate⏱ ~12 minbash

What you'll learn

  • Separate a state lock, state data problem, and resource-address problem
  • Use read-only state inspection commands before considering a state mutation
  • Recover a wrong resource address with a reviewed moved block or state mv
  • Restore a corrupt state from a verified backend version without hand-editing JSON
  • Protect state integrity, confidentiality, and recovery evidence in production

Prerequisites

None — start here.

Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13

Not yet marked complete on this device.

Terraform state is a structured record of the objects Terraform manages and the relationships between them. State is not a substitute for the configuration, and the lock that protects state is not the same thing as the state data itself. Troubleshooting state issues means identifying which of those things is wrong before deciding whether to wait, move an address, import an object, or restore a version.

A lock prevents simultaneous state writes. A corrupted state cannot be read or interpreted reliably. A wrong resource address makes Terraform look for a managed object under a different identity. These failures have different symptoms and different safe recoveries.

State, lock, and address are separate boundaries

Backend
  |-- state data: resource instances, attributes, outputs, serial
  |-- lock metadata: lock ID, owner, operation, timestamp
  `-- configuration: resource addresses and dependency graph

Terraform reads the state for the selected backend and workspace, then builds a plan from configuration, state, and the real infrastructure reported by providers. A lock failure happens before the intended graph can be applied. A wrong address is a consistency problem between configuration and state. Corruption is a data-integrity problem in the state representation.

The state diagnostic order

Use this order consistently:

  1. Preserve the lock evidence. Record the lock ID, owner, operation, timestamp, backend, and workspace. Do not force-unlock yet.
  2. Confirm the address. Record the exact resource address and instance key, including module names and for_each keys.
  3. Inspect before mutating. Use state list, state show, and a refresh-only plan. Save a read-only copy if the investigation needs a local comparison.
  4. Classify the failure. Lock, malformed state, address mismatch, missing import, or backend access problem.
  5. Protect the recovery. Create a backend or state backup, confirm ownership of the real object, and agree on the rollback.
  6. Apply the narrow fix. Move an address, import an untracked object, restore a verified state version, or correct the backend.
  7. Verify. Run a plan, check the state serial, and confirm the object in the real platform.

1. Lock contention

A remote backend reports contention with details similar to:

Error: Error acquiring the state lock

Error message: lock held by another process
Lock Info:
  ID:        7c2e1c91-4cc0-2b1d-a64b-4db28d71b82d
  Operation: OperationTypeApply
  Who:       ci-job-1842
  Version:   1.9.8
  Created:   2026-08-13 02:14:03 +0000 UTC
  Path:     infra-production

The lock is a coordination mechanism, not an error in the state JSON. First check the owner and process. A short wait is safer than force-unlock when the operation is active. The following command is read-only, but it does not tell you which process owns a remote lock; use the job or process information named by the error and the backend’s own lock diagnostics.

READ-ONLY

ps -ef | grep '[t]erraform'
terraform plan -input=false -no-color -refresh-only

If the lock is confirmed stale, the operator who owns the change can use the exact lock ID with the approved command. The command may ask for confirmation.

SERVICE-IMPACT — can permit concurrent state writes if used against a live lock. Use only after verifying the owner is gone and recording the decision.

terraform force-unlock '7c2e1c91-4cc0-2b1d-a64b-4db28d71b82d'

force-unlock is not a routine error recovery. Disabling the lock with -lock=false is not a substitute for investigating the owner.

2. State data corruption

A corrupt local state or backend version can produce a parse error, an unexpected serialised value, or a failure while loading a resource address. terraform validate checks configuration and providers; it does not prove that the current state is readable or correct.

READ-ONLY

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

If state pull fails or the state is visibly truncated, stop all state writes. Preserve the current version and the backend metadata. For a remote backend, use its native versioning or object-recovery mechanism to select a known good state version. There is no universal Terraform command that safely repairs arbitrary state corruption, and hand-editing JSON does not verify that the changed entry matches the real object.

After a restore, run a refresh-only plan. The plan should be explainable against the configuration and the provider. If the restored version is old enough to include a resource that no longer exists, do not force the plan through; resolve the ownership question first.

3. Wrong resource address

A rename from aws_instance.web to aws_instance.api normally makes Terraform plan a destroy and create. That is a visible clue that the configuration address changed, not proof that the instance must be replaced.

READ-ONLY

terraform state list | grep -E -e 'aws_instance\.web' -e 'aws_instance\.api'
terraform state show 'aws_instance.web'
terraform plan -input=false -no-color -refresh-only

Prefer a moved block in the configuration so the refactor is reviewable and repeatable:

resource "aws_instance" "api" {
  ami           = var.api_ami
  instance_type = "t3.small"
}

moved {
  from = aws_instance.web
  to   = aws_instance.api
}

The block changes the address in Terraform’s model. It does not create or destroy the real instance. Run a plan and expect Terraform to report the move rather than a replacement. For an operational recovery where the configuration cannot be changed immediately, use the imperative form with an explicit backup.

CONFIGURATION — changes state address only. It does not change the real object. A backup is mandatory.

terraform state mv -backup-file=/tmp/state-before-move.tfstate \
  'aws_instance.web' 'aws_instance.api'
terraform state list | grep -E -e 'aws_instance\.web' -e 'aws_instance\.api'
terraform plan -input=false -no-color

If the source and destination belong to different resource types or different real objects, state mv is the wrong tool. Use a reviewed moved block only for a compatible address change, and use import when Terraform does not already own the object.

4. Missing ownership or untracked object

A resource may exist in the platform but not in state after a manual creation, an incomplete adoption, or a lost mapping. The diagnostic symptom is a plan to create an object that already exists.

CONFIGURATION — records an existing real object in state. The cloud object is not recreated, but the provider may need permission to read or modify it.

terraform import 'aws_instance.web' 'i-0123456789abcdef0'
terraform plan -input=false -no-color

Confirm the provider resource type, region, and real identifier before importing. Importing an identifier into the wrong address can make the next plan attempt to modify or replace the wrong object.

5. State command damage

state rm removes a resource from state. It does not destroy the object, and it can leave an unmanaged resource behind. It is sometimes appropriate during a deliberate adoption, but it is never a diagnostic shortcut. If it is approved, use a backup and verify the plan before any later apply.

DATA-LOSS-RISK — removes the state entry. Use only after confirming that the real object is intentionally unmanaged and that the backup is recoverable.

terraform state rm -backup-file=/tmp/state-before-rm.tfstate \
  'aws_instance.web'
terraform plan -input=false -no-color

Never use a text editor to repair a provider ID, dependency, or resource schema. State is an internal data structure, not a hand-maintained configuration format.

Verification commands and expected evidence

Use these checks after a state recovery. The grep output is illustrative and the serial number will differ between workspaces.

READ-ONLY

terraform state list
terraform state show 'aws_instance.api'
terraform state pull | jq '.serial'
terraform plan -input=false -no-color -refresh-only

For the moved example, the expected shape is:

aws_instance.api
Plan: 0 to add, 0 to change, 0 to destroy

If the plan shows a destroy or create after a pure address move, stop. The source address, destination address, module path, or resource type is wrong. Do not repeat the state move until the mismatch is explained.

Security and performance

State is a sensitive production artefact. Use a remote backend with encryption, versioning, least-privilege read and write roles, and an audit trail. Keep the lock and state access roles separate where the platform supports it. Back up state independently of the local working directory and test restoration before an outage forces you to use it.

Large state files increase lock duration, refresh time, memory use, and the cost of every plan. Keep resource count bounded, use stable identities, and avoid importing a whole estate into one state file. A refresh-only plan helps separate drift from an intentional action. Do not use -refresh=false for routine production plans just to make them faster, because it can hide the mismatch you are trying to diagnose.

Production guidance

  • Enable backend versioning and test a state restore in staging.
  • Keep lock timeouts explicit, but never use them to justify force-unlocking a live operation.
  • Prefer moved blocks for permanent configuration refactors; use state mv only for a controlled operational recovery with a backup.
  • Protect state files and saved plans as production data; a local terraform.tfstate is not a recovery strategy.
  • After a state incident, verify the real platform object, its state address, the backend serial, and the next plan before closing the incident.

Verification

  • You can distinguish a lock failure from malformed state and a wrong resource address.
  • You can inspect state and generate a refresh-only plan before changing state.
  • You can use a moved block or backed-up state mv for a compatible address change.
  • You know when to use import and why state rm can create an unmanaged object.
  • You can restore a verified state version without editing JSON by hand.
  • You can verify the state serial, plan, and real object after recovery.

Knowledge check · 7 questions

  1. Q1. What should you record first when Terraform reports a state lock?

  2. Q2. What is the safe first response to a confirmed stale state lock?

  3. Q3. `terraform validate` checks the configuration and its provider requirements, so it proves nothing about whether state is readable or matches the real platform.

  4. Q4. Which symptoms should be classified as state incidents rather than ordinary configuration errors? (Select all that apply.)

  5. Q5. A resource was renamed in configuration and the next plan proposes destroying the live object. The source and destination have the same type and provider. What is the preferred permanent fix?

  6. Q6. What is the correct recovery for a malformed remote state version?

  7. Q7. What should happen after a resource address is moved with `terraform state mv`?

Passing score: 75%. Answers are checked in this browser.