TerraformPilot

DevOps

Fix Terraform Error - HTTP 409 State Lock Conflict

Learn how to fix HTTP 409 state lock conflict errors in Terraform Cloud, Enterprise, and HTTP backends. Includes force-unlock and prevention strategies.

LLuca Berton3 min read

Quick Answer

#

HTTP 409 means another Terraform operation holds the state lock. Wait for the active run to complete, cancel it if it's stuck, or use terraform force-unlock as a last resort. Never run multiple applies against the same workspace simultaneously.

The Error

#

When running terraform plan or terraform apply, you encounter:

Error: Error locking state: HTTP remote state endpoint returned 409
Lock Info:
  ID:        a1b2c3d4-e5f6-7890-abcd-ef1234567890
  Path:      terraform-state/production/terraform.tfstate
  Operation: OperationTypeApply
  Who:       user@hostname
  Version:   1.8.0
  Created:   2025-10-28 14:30:00.000000 UTC
  Info:

In Terraform Cloud or Enterprise, you may see:

Error: Error locking state: status code 409
Error: workspace already locked (lock ID: "ws-abc123")

What Causes This Error

#

1. Another Apply Is Running

#

A teammate or CI/CD pipeline is currently running terraform apply on the same workspace. The lock prevents concurrent writes that would corrupt the state.

2. Previous Run Crashed Without Releasing the Lock

#

Terraform was killed (Ctrl+C during apply, CI timeout, network drop) and didn't clean up the lock.

3. Stale Lock in Terraform Cloud

#

A Terraform Cloud run got stuck in "planning" or "applying" status and never completed.

4. Multiple CI/CD Pipelines Triggered Simultaneously

#

Two merge events triggered parallel pipelines that both try to apply against the same workspace.

How to Fix It

#

Solution 1: Wait for the Active Run to Complete

#

Check who holds the lock from the error output (Who: field) and wait:

# The lock info tells you who and when
# Wait 5-10 minutes, then retry
terraform plan

Solution 2: Cancel the Active Run in Terraform Cloud

#

Via the UI:

  1. Go to Workspaces → select the workspace
  2. Click Runs in the sidebar
  3. Find the active run and click Cancel Run
  4. Confirm the cancellation

Via the API:

# Get the current run ID
curl -s \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/runs?page[size]=1" \
  | jq -r '.data[0].id'
 
# Cancel the run
curl -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/runs/RUN_ID/actions/cancel"

Solution 3: Unlock the Workspace in Terraform Cloud

#

If the run is stuck and won't cancel:

# Force-cancel (waits 10s then kills)
curl -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/runs/RUN_ID/actions/force-cancel"
 
# Or unlock the workspace directly
curl -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/actions/unlock"

Solution 4: Force-Unlock for Remote Backends (S3, GCS, etc.)

#

Use the lock ID from the error message:

# Force-unlock using the lock ID from the error
terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890

Important: Only use force-unlock when you're certain no other operation is running. If another apply is actually in progress, unlocking can corrupt your state.

# Verify no apply is running first
# Check with your team
# Check CI/CD pipeline status
# Then force-unlock
terraform force-unlock -force a1b2c3d4-e5f6-7890-abcd-ef1234567890

Solution 5: For DynamoDB Lock Table (S3 Backend)

#

If the lock is stuck in DynamoDB:

# Check who holds the lock
aws dynamodb get-item \
  --table-name terraform-lock \
  --key '{"LockID": {"S": "my-bucket/terraform.tfstate"}}' \
  --output json
 
# Delete the stale lock (last resort)
aws dynamodb delete-item \
  --table-name terraform-lock \
  --key '{"LockID": {"S": "my-bucket/terraform.tfstate"}}'

Preventing Concurrent Runs

#

CI/CD Pipeline Serialization

#

Ensure only one pipeline runs per workspace:

# GitLab CI — use resource_group to serialize
deploy:
  stage: deploy
  resource_group: production
  script:
    - terraform apply -auto-approve
 
# GitHub Actions — use concurrency
jobs:
  deploy:
    concurrency:
      group: terraform-production
      cancel-in-progress: false
    steps:
      - run: terraform apply -auto-approve

Terraform Cloud Auto-Queue

#

Terraform Cloud automatically queues runs. If a run is already in progress, new runs wait. Configure this in workspace settings:

  • Execution Mode: Remote
  • Apply Method: Auto apply or Manual apply
  • Run Triggers: Configure dependencies between workspaces

Lock Timeout Strategy

#

Add a retry wrapper to your CI/CD scripts:

#!/bin/bash
MAX_RETRIES=5
RETRY_DELAY=60
 
for i in $(seq 1 $MAX_RETRIES); do
  terraform apply -auto-approve && exit 0
  echo "Attempt $i failed. Lock may be held. Retrying in ${RETRY_DELAY}s..."
  sleep $RETRY_DELAY
done
 
echo "ERROR: Failed after $MAX_RETRIES attempts"
exit 1

Troubleshooting Checklist

#
  1. ✅ Check the Who: field in the lock info — is it a teammate or CI?
  2. ✅ Is there an active run in Terraform Cloud / your CI pipeline?
  3. ✅ When was the lock created? If hours ago, it's likely stale
  4. ✅ Did a previous run crash? Check CI logs for timeouts
  5. ✅ Can you contact the lock holder to confirm they're done?
  6. ✅ Is your CI/CD serializing runs per workspace?

Prevention Tips

#
  • Serialize CI/CD pipelines — use resource_group (GitLab) or concurrency (GitHub Actions)
  • Set apply timeouts — prevent runs from hanging forever
  • Use Terraform Cloud run queuing — built-in serialization
  • Communicate with your team — coordinate who applies when
  • Add retry logic — handle transient lock contention gracefully
#

Conclusion

#

HTTP 409 is Terraform's state locking doing its job — protecting your state from concurrent writes. If it's an active run, wait for it. If it's a stale lock, force-unlock it. Then set up pipeline serialization so it doesn't happen again.

#Terraform#Troubleshooting#DevOps#Error Fix#Infrastructure as Code

Share this article