Terraform Error: ConditionalCheckFailedException - How to Fix DynamoDB State Locks
Fix Terraform state lock errors (ConditionalCheckFailedException) with S3/DynamoDB backends. Learn how to diagnose, forcefully unlock, and prevent locking issue
- Root Cause 1: A previous Terraform run (plan, apply, or destroy) was violently interrupted, killed, or timed out before releasing the DynamoDB lock.
- Root Cause 2: Two CI/CD pipelines or team members are attempting concurrent Terraform operations on the exact same state file, triggering legitimate protection.
- Root Cause 3: Network connectivity drops or AWS API rate limiting prevented the Terraform client from successfully deleting the lock item from the DynamoDB table.
- Quick Fix: Use 'terraform force-unlock <LOCK_ID>' to remove the stale lock, but ONLY after definitively verifying no other processes are actively modifying the infrastructure.
| Method | When to Use | Time | Risk |
|---|---|---|---|
| Wait and Retry | CI/CD pipeline is currently running a known long operation. | Variable | Low |
| terraform force-unlock | A previous run crashed or was killed, leaving a stale lock. | < 1 min | Medium (Risk of state corruption if concurrent runs exist) |
| AWS Console/CLI (Manual Delete) | Terraform CLI is broken or you lost access to the workspace. | 2-5 mins | High (Bypasses Terraform safety checks) |
| Disable Locking (Local Run) | Emergency read-only operations (terraform plan -lock=false). | Immediate | Low (for plan), Extreme (for apply) |
Understanding the Error: Terraform State File Locking
When working with Terraform in a team environment, managing the state file is one of the most critical aspects of your infrastructure-as-code (IaC) lifecycle. To prevent race conditions and state corruption, Terraform implements a locking mechanism. When using the popular AWS S3 backend, this locking mechanism is offloaded to Amazon DynamoDB.
If you encounter the ConditionalCheckFailedException, it means your Terraform client attempted to acquire a lock on the state file but failed because a lock already exists.
The full error typically looks like this:
Error locking state: Error acquiring the state lock: ConditionalCheckFailedException: The conditional request failed
Lock Info:
ID: 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
Path: my-terraform-state-bucket/env/prod/terraform.tfstate
Operation: OperationTypeApply
Who: jane.doe@janes-macbook-pro.local
Version: 1.5.7
Created: 2023-10-27T10:00:00.000Z
Info:
How DynamoDB Terraform Lock Works
To understand the fix, you must first understand the architecture. The terraform s3 backend dynamodb lock setup uses an S3 bucket to store the actual terraform.tfstate JSON file and a DynamoDB table to store the lock metadata.
- Initialization: When you run
terraform planorterraform apply, Terraform reaches out to the specified DynamoDB table. - Conditional Put: Terraform attempts a
PutItemoperation on the DynamoDB table with aConditionExpressionofattribute_not_exists(LockID). - Success: If no item with that
LockIDexists, the write succeeds. Terraform has acquired the lock and proceeds with the operation. - Failure (The Error): If an item with that
LockIDdoes exist, DynamoDB rejects the write with aConditionalCheckFailedException. Terraform intercepts this, reads the existing lock item to get the metadata (Who, Operation, Created), and prints theError locking statemessage to your terminal.
Step 1: Diagnose the Root Cause
Before blindly unlocking the state, you must determine why the lock exists. There are two primary scenarios:
Scenario A: A Legitimate Concurrent Operation
Another team member or your CI/CD pipeline (e.g., GitHub Actions, GitLab CI, Jenkins) is currently running a terraform plan or terraform apply. In this scenario, the lock is functioning exactly as designed—it is protecting your infrastructure from concurrent modifications.
Scenario B: A Stale or Orphaned Lock A previous Terraform operation was violently interrupted. This happens if:
- A developer pressed
Ctrl+Cmultiple times during an apply, killing the Terraform process before it could run its cleanup routines. - The CI/CD pipeline runner crashed, ran out of memory (OOM), or reached its maximum execution timeout.
- The machine running Terraform lost internet connectivity exactly after modifying the S3 state but before it could send the
DeleteItemrequest to DynamoDB.
To diagnose, look at the Who and Created fields in the error message. If the lock was created 3 days ago by a developer who is currently on vacation, it is a stale lock. If it was created 2 minutes ago by your CI/CD service account, a pipeline is likely running.
Step 2: Fix the Terraform Lock
Once you have definitively confirmed that no active processes are modifying the infrastructure, you can proceed to release the lock.
Method 1: The Native terraform force-unlock Command (Recommended)
Terraform provides a built-in command to handle stale locks. You will need the Lock ID provided in the error message.
- Copy the Lock ID from your terminal output (e.g.,
1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d). - Run the force-unlock command:
terraform force-unlock 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d - Terraform will prompt you for confirmation:
Do you really want to force-unlock?Terraform will remove the lock on the remote state.This will allow local Terraform commands to modify this state, even though it may be still be in use. Only 'yes' will be accepted to confirm. - Type
yesand press Enter.
Terraform will connect to DynamoDB and delete the item, freeing the state file for your next operation.
Method 2: Manually Removing the Lock via AWS CLI / Console
If your local Terraform environment is corrupted, or if you are automating the cleanup of locks in a script, you can interact with DynamoDB directly.
Using the AWS CLI:
You need to know the exact LockID string used as the primary key in DynamoDB. For the S3 backend, the LockID is exactly the S3 path to your state file.
aws dynamodb delete-item \
--table-name your-dynamodb-lock-table-name \
--key '{"LockID": {"S": "my-bucket-name/path/to/my/terraform.tfstate"}}' \
--region us-east-1
Using the AWS Console:
- Log into the AWS Management Console.
- Navigate to DynamoDB > Tables > [Your Lock Table] > Explore table items.
- Find the item where the
LockIDmatches your state file path. - Select the checkbox next to the item.
- Click the "Actions" dropdown and select "Delete items".
Method 3: Bypassing the Lock (Read-Only Operations)
Sometimes you just want to see what changes would be made, and you don't care if a lock is currently held. You can instruct Terraform to ignore the locking mechanism entirely.
Warning: NEVER use this for terraform apply in a team environment. It should only be used for terraform plan.
terraform plan -lock=false
This command skips the DynamoDB conditional check and directly reads the terraform.tfstate from S3.
Step 3: Prevent Future Locking Issues
While terraform force-unlock is a lifesaver, relying on it indicates a flaw in your operational practices. Here is how to minimize stale locks:
1. Implement Graceful CI/CD Timeouts:
Ensure your CI/CD pipelines send a SIGINT (graceful termination) rather than a SIGKILL (immediate termination) when a timeout occurs. Terraform intercepts SIGINT and attempts to release the DynamoDB lock before shutting down.
2. Avoid Local Execution:
Transition your team to a "GitOps" workflow where Terraform is only executed via automated pipelines. When developers run terraform apply locally on sketchy Wi-Fi connections, the chances of a dropped connection leaving a stale lock skyrocket.
3. State File Segmentation:
If you frequently experience legitimate lock contention (developers waiting on each other), your state file is too large. Break your monolithic terraform.tfstate into smaller, independently manageable workspaces (e.g., separate state files for networking, database, and application layers). This reduces the blast radius of a lock.
Best Practices for the Terraform S3 Backend with DynamoDB
Setting up the backend correctly the first time prevents weird edge cases. Ensure your backend.tf is configured optimally:
terraform {
backend "s3" {
bucket = "my-org-terraform-state-bucket"
key = "core-infrastructure/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "terraform-state-locks"
}
}
Crucial DynamoDB Table Requirements:
- The table MUST have a partition key named
LockIDof type String (S). - You do NOT need a sort key.
- Provisioned throughput can be extremely low (1 RC / 1 WCU), or you can use On-Demand billing, as lock operations are tiny and infrequent compared to standard database workloads.
- Consider enabling Point-in-Time Recovery (PITR) on both the S3 bucket and the DynamoDB table for disaster recovery compliance.
By understanding the mechanics of the ConditionalCheckFailedException, respecting the safety boundaries it provides, and knowing how to safely remove stale items, you can maintain a resilient and conflict-free Terraform deployment pipeline.
Frequently Asked Questions
# 1. View the error output to get the Lock ID
# Error locking state: Error acquiring the state lock: ConditionalCheckFailedException...
# Lock Info:
# ID: d29fa8f4-b9db-424a-9db6-7fdb96e70eb6
# 2. Verify no active runs in your CI/CD (GitHub Actions, GitLab CI, Jenkins)
# 3. Forcefully remove the lock using the Terraform CLI
terraform force-unlock d29fa8f4-b9db-424a-9db6-7fdb96e70eb6
# 4. (Alternative) Remove the lock directly via AWS CLI if Terraform is stuck
aws dynamodb delete-item \
--table-name my-terraform-lock-table \
--key '{"LockID": {"S": "my-s3-bucket/path/to/terraform.tfstate"}}'
# 5. Run a plan without locking (READ-ONLY) to check current state
terraform plan -lock=falseError Medic Editorial
The Error Medic Editorial team consists of senior DevOps engineers, Site Reliability Engineers (SREs), and Cloud Architects dedicated to solving the most complex infrastructure-as-code and deployment issues.