Introduction

Terraform uses state locking to prevent concurrent modifications that could corrupt infrastructure state. When a lock is held by another process (or orphaned), subsequent operations fail with lock acquisition timeouts. This is common in CI/CD pipelines or when Terraform crashes mid-operation.

Symptoms

  • Error: Error locking state: Error acquiring the state lock
  • timeout while waiting for state to become unlocked
  • Lock ID shown in error message: Lock Info: ID: abc123
  • Previous Terraform process crashed or was killed
  • CI/CD pipeline timed out leaving lock behind
  • Multiple team members cannot run Terraform simultaneously
  • DynamoDB lock table shows active lock items

Common Causes

  • Previous Terraform process killed or crashed
  • CI/CD pipeline interrupted mid-execution
  • Network timeout during state unlock operation
  • Multiple CI runners attempting concurrent runs
  • Orphaned lock in DynamoDB, Consul, or etcd
  • Different Terraform versions trying to access same state
  • Backend credentials lacking unlock permissions

Step-by-Step Fix

  1. 1.Identify lock information:
  2. 2.```bash
  3. 3.terraform plan
  4. 4.# Error shows: Lock Info: ID: xxxx-xxxx-xxxx
  5. 5.# Path: terraform.tfstate
  6. 6.# Created: 2026-04-09 10:00:00 UTC
  7. 7.`
  8. 8.Check for running Terraform processes:
  9. 9.```bash
  10. 10.# Check if another process is actually running
  11. 11.ps aux | grep terraform

# Check CI/CD pipeline status # If no active process, lock is orphaned ```

  1. 1.Force unlock the state (only if no process is running):
  2. 2.```bash
  3. 3.# Using the lock ID from error message
  4. 4.terraform force-unlock xxxx-xxxx-xxxx

# Confirm with -force flag terraform force-unlock -force xxxx-xxxx-xxxx ```

  1. 1.For S3 backend with DynamoDB locking, check DynamoDB directly:
  2. 2.```bash
  3. 3.aws dynamodb scan \
  4. 4.--table-name terraform-state-lock \
  5. 5.--region us-east-1

# Or get specific item aws dynamodb get-item \ --table-name terraform-state-lock \ --key '{"LockID":{"S":"terraform-state/my-project"}}' ```

  1. 1.Manually remove orphaned lock from DynamoDB:
  2. 2.```bash
  3. 3.aws dynamodb delete-item \
  4. 4.--table-name terraform-state-lock \
  5. 5.--key '{"LockID":{"S":"terraform-state/my-project"}}'
  6. 6.`
  7. 7.For Consul backend, check and remove lock:
  8. 8.```bash
  9. 9.consul kv get terraform/my-project/.lock
  10. 10.consul kv delete terraform/my-project/.lock
  11. 11.`
  12. 12.For Azure backend, check blob lease:
  13. 13.```bash
  14. 14.az storage blob show \
  15. 15.--container-name tfstate \
  16. 16.--name myproject.tfstate \
  17. 17.--account-name mystorageaccount

# Break lease if needed az storage blob lease break \ --container-name tfstate \ --blob-name myproject.tfstate \ --account-name mystorageaccount ```

  1. 1.Configure proper lock timeout in backend config:
  2. 2.```hcl
  3. 3.terraform {
  4. 4.backend "s3" {
  5. 5.bucket = "my-tfstate-bucket"
  6. 6.key = "myproject/terraform.tfstate"
  7. 7.region = "us-east-1"
  8. 8.dynamodb_table = "terraform-state-lock"
  9. 9.encrypt = true

# Increase timeout for slow networks lock_timeout = "10m" } } ```

  1. 1.Implement CI/CD safeguards:
  2. 2.```yaml
  3. 3.# GitLab CI example with lock timeout
  4. 4.terraform:apply:
  5. 5.script:
  6. 6.- terraform init
  7. 7.- timeout 1800 terraform apply -auto-approve || terraform force-unlock -force $(terraform show -json 2>&1 | grep -oP 'ID: \K[^ ]+')
  8. 8.retry: 1
  9. 9.`
  10. 10.Enable state locking notifications:
  11. 11.```bash
  12. 12.# Wrapper script with Slack notification
  13. 13.#!/bin/bash
  14. 14.LOCK_ID=$(terraform plan 2>&1 | grep -oP 'ID: \K[^ ]+' || true)
  15. 15.if [ -n "$LOCK_ID" ]; then
  16. 16.curl -X POST -H 'Content-type: application/json' \
  17. 17.--data '{"text":"Terraform state locked by '"$LOCK_ID"'"}' \
  18. 18.$SLACK_WEBHOOK
  19. 19.fi
  20. 20.`

Prevention

  • Always use trap to unlock on script exit in CI/CD
  • Implement Terraform wrapper scripts with proper error handling
  • Use dedicated lock table per project
  • Configure appropriate lock timeouts for your environment
  • Use Terraform Cloud or Enterprise for managed locking
  • Implement state locking monitoring with notifications
  • Regular cleanup of orphaned locks using automation
  • Use -lock=false only in emergency (not recommended)
  • Ensure proper IAM permissions for lock table access
  • Use unique workspace names to avoid cross-project conflicts

CI/CD Best Practices

  • Serialize Terraform runs using pipeline dependencies
  • Implement terraform plan before apply to catch lock issues early
  • Use TF_LOG=DEBUG for troubleshooting in CI
  • Set job timeouts shorter than lock timeout
  • Implement state locking health checks
  • Use dedicated runner tags for Terraform jobs
  • Store lock table in same region as state bucket