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 locktimeout 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.Identify lock information:
- 2.```bash
- 3.terraform plan
- 4.# Error shows: Lock Info: ID: xxxx-xxxx-xxxx
- 5.# Path: terraform.tfstate
- 6.# Created: 2026-04-09 10:00:00 UTC
- 7.
` - 8.Check for running Terraform processes:
- 9.```bash
- 10.# Check if another process is actually running
- 11.ps aux | grep terraform
# Check CI/CD pipeline status # If no active process, lock is orphaned ```
- 1.Force unlock the state (only if no process is running):
- 2.```bash
- 3.# Using the lock ID from error message
- 4.terraform force-unlock xxxx-xxxx-xxxx
# Confirm with -force flag terraform force-unlock -force xxxx-xxxx-xxxx ```
- 1.For S3 backend with DynamoDB locking, check DynamoDB directly:
- 2.```bash
- 3.aws dynamodb scan \
- 4.--table-name terraform-state-lock \
- 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.Manually remove orphaned lock from DynamoDB:
- 2.```bash
- 3.aws dynamodb delete-item \
- 4.--table-name terraform-state-lock \
- 5.--key '{"LockID":{"S":"terraform-state/my-project"}}'
- 6.
` - 7.For Consul backend, check and remove lock:
- 8.```bash
- 9.consul kv get terraform/my-project/.lock
- 10.consul kv delete terraform/my-project/.lock
- 11.
` - 12.For Azure backend, check blob lease:
- 13.```bash
- 14.az storage blob show \
- 15.--container-name tfstate \
- 16.--name myproject.tfstate \
- 17.--account-name mystorageaccount
# Break lease if needed az storage blob lease break \ --container-name tfstate \ --blob-name myproject.tfstate \ --account-name mystorageaccount ```
- 1.Configure proper lock timeout in backend config:
- 2.```hcl
- 3.terraform {
- 4.backend "s3" {
- 5.bucket = "my-tfstate-bucket"
- 6.key = "myproject/terraform.tfstate"
- 7.region = "us-east-1"
- 8.dynamodb_table = "terraform-state-lock"
- 9.encrypt = true
# Increase timeout for slow networks lock_timeout = "10m" } } ```
- 1.Implement CI/CD safeguards:
- 2.```yaml
- 3.# GitLab CI example with lock timeout
- 4.terraform:apply:
- 5.script:
- 6.- terraform init
- 7.- timeout 1800 terraform apply -auto-approve || terraform force-unlock -force $(terraform show -json 2>&1 | grep -oP 'ID: \K[^ ]+')
- 8.retry: 1
- 9.
` - 10.Enable state locking notifications:
- 11.```bash
- 12.# Wrapper script with Slack notification
- 13.#!/bin/bash
- 14.LOCK_ID=$(terraform plan 2>&1 | grep -oP 'ID: \K[^ ]+' || true)
- 15.if [ -n "$LOCK_ID" ]; then
- 16.curl -X POST -H 'Content-type: application/json' \
- 17.--data '{"text":"Terraform state locked by '"$LOCK_ID"'"}' \
- 18.$SLACK_WEBHOOK
- 19.fi
- 20.
`
Prevention
- Always use
trapto 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=falseonly 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 planbeforeapplyto catch lock issues early - Use
TF_LOG=DEBUGfor 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