Terraform state locks prevent concurrent modifications but can cause frustrating errors. This guide covers all state lock scenarios and safe resolution procedures.
Understanding State Locking
Normal Flow:
terraform plan → Acquire Lock → Read State → Release Lock
terraform apply → Acquire Lock → Read State → Modify → Write State → Release Lock
Lock Stuck Flow:
terraform apply → Acquire Lock → CRASH → Lock Remains 🔒
↓
Next run: "Lock acquisition failed"
Error: Lock Acquisition Failed
Symptom:
Error: Error acquiring the state lock
Error message: ConditionalCheckFailedException: The conditional request failed
Lock Info:
ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Path: terraform-state/prod/terraform.tfstate
Operation: OperationTypeApply
Who: user@hostname
Version: 1.6.0
Created: 2026-01-19 10:30:45.123456 +0000 UTC
Cause: Another process holds the lock, or a previous process crashed without releasing.
Solution 1 - Check if another process is running:
# Check for running Terraform processes
ps aux | grep terraform
# Check who has the lock (from error message)
# "Who: user@hostname" tells you the machine
# If it's a CI/CD pipeline, check if a job is still runningSolution 2 - Force unlock (USE WITH CAUTION):
# Only use if you're CERTAIN no other process is running
terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890
# With auto-approve (even more dangerous)
terraform force-unlock -force a1b2c3d4-e5f6-7890-abcd-ef1234567890WARNING: Force unlocking while another process is running can corrupt your state!
Error: State File Doesn't Exist
Symptom:
Error: Failed to load state: state snapshot was created by Terraform v1.6.0,
which is newer than current v1.5.0
Error: Unable to find remote state
Solution 1 - Initialize backend:
# Reinitialize with backend
terraform init -reconfigure
# If migrating backends
terraform init -migrate-stateSolution 2 - Check backend configuration:
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks" # For locking
}
}Error: DynamoDB Lock Table Issues
Symptom:
Error: Error acquiring the state lock
Error: ResourceNotFoundException: Requested resource not found
Error: Error releasing the state lock
AccessDeniedException: User is not authorized to perform dynamodb:DeleteItem
Cause 1: Lock table doesn't exist
# Create the DynamoDB table
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "Terraform State Lock Table"
}
}Cause 2: IAM permissions missing
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:DescribeTable"
],
"Resource": "arn:aws:dynamodb:*:*:table/terraform-locks"
}
]
}Cause 3: Stale lock in DynamoDB
# View the lock item
aws dynamodb get-item \
--table-name terraform-locks \
--key '{"LockID": {"S": "terraform-state/prod/terraform.tfstate"}}'
# Delete stale lock (DANGEROUS - ensure nothing is running!)
aws dynamodb delete-item \
--table-name terraform-locks \
--key '{"LockID": {"S": "terraform-state/prod/terraform.tfstate"}}'Error: S3 Backend Permission Denied
Symptom:
Error: Failed to load state: AccessDenied: Access Denied
Error: Error saving state: AccessDenied: Access Denied
Solution - Required S3 permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-terraform-state",
"arn:aws:s3:::my-terraform-state/*"
]
}
]
}Check bucket policy:
# Verify access
aws s3 ls s3://my-terraform-state/
aws s3 cp s3://my-terraform-state/prod/terraform.tfstate /tmp/test-stateError: State Version Mismatch
Symptom:
Error: state snapshot was created by Terraform v1.6.0,
which is newer than current v1.5.0; upgrade to Terraform v1.6.0 or greater
Error: Unsupported state file version
Solution 1 - Upgrade Terraform:
# Using tfenv
tfenv install 1.6.0
tfenv use 1.6.0
# Verify version
terraform versionSolution 2 - Use version constraints:
# versions.tf
terraform {
required_version = ">= 1.5.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}Error: Concurrent Apply Detected
Symptom:
Error: Error locking state: Error acquiring the state lock:
state lock already held by another process
Applied changes may have been made.
Prevention - CI/CD serialization:
# GitHub Actions - use concurrency
name: Terraform
on: push
concurrency:
group: terraform-${{ github.ref }}
cancel-in-progress: false # Don't cancel, queue instead
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform apply -auto-approve# GitLab CI - use resource_group
terraform_apply:
stage: deploy
resource_group: production # Only one job at a time
script:
- terraform apply -auto-approveSafe State Recovery Procedures
Procedure 1: Investigate before unlocking
# 1. Get lock info
terraform plan 2>&1 | grep -A 10 "Lock Info"
# 2. Check if process is still running on the machine mentioned
ssh user@hostname "ps aux | grep terraform"
# 3. Check CI/CD for running jobs
# 4. Only force-unlock if confirmed nothing is runningProcedure 2: State backup before unlock
# 1. Backup current state
terraform state pull > backup-$(date +%Y%m%d-%H%M%S).tfstate
# 2. Force unlock
terraform force-unlock LOCK_ID
# 3. Verify state
terraform planProcedure 3: Recover corrupted state
# 1. List state versions (S3)
aws s3api list-object-versions \
--bucket my-terraform-state \
--prefix prod/terraform.tfstate
# 2. Download previous version
aws s3api get-object \
--bucket my-terraform-state \
--key prod/terraform.tfstate \
--version-id VERSION_ID \
recovered-state.tfstate
# 3. Push recovered state
terraform state push recovered-state.tfstatePreventing State Lock Issues
Use workspaces for isolation:
# Create workspace per environment
terraform workspace new production
terraform workspace new staging
# Each workspace has its own state
terraform workspace select production
terraform applyImplement lock timeouts:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
# Custom retry settings (in seconds)
skip_metadata_api_check = true
}
}Add CI/CD safeguards:
# Pre-apply check
- name: Check for stale locks
run: |
LOCK_AGE=$(aws dynamodb get-item \
--table-name terraform-locks \
--key '{"LockID": {"S": "terraform-state/prod/terraform.tfstate"}}' \
--query 'Item.Created.S' --output text 2>/dev/null)
if [ -n "$LOCK_AGE" ]; then
LOCK_TIMESTAMP=$(date -d "$LOCK_AGE" +%s)
CURRENT_TIMESTAMP=$(date +%s)
AGE_MINUTES=$(( (CURRENT_TIMESTAMP - LOCK_TIMESTAMP) / 60 ))
if [ $AGE_MINUTES -gt 60 ]; then
echo "WARNING: Lock is $AGE_MINUTES minutes old!"
exit 1
fi
fiQuick Reference: Lock Commands
| Scenario | Command |
|----------|---------|
| View lock info | terraform plan (error shows lock details) |
| Force unlock | terraform force-unlock LOCK_ID |
| Pull state | terraform state pull > backup.tfstate |
| Push state | terraform state push backup.tfstate |
| List state | terraform state list |
| Show state item | terraform state show resource.name |
Enterprise Terraform Challenges?
Managing Terraform at scale requires careful state management strategies. Our team specializes in:
- Multi-team Terraform workflows
- State migration and recovery
- GitOps pipeline implementation
- Terraform Cloud/Enterprise setup