Fix Terraform Error: CloudWatch Log Group Already Exists
Fix terraform CloudWatch Log Group ResourceAlreadyExistsException. Import orphaned log groups, prevent Lambda auto-creation
DevOps
Learn how to fix HTTP 409 state lock conflict errors in Terraform Cloud, Enterprise, and HTTP backends. Includes force-unlock and prevention strategies.
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.
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 409Error: workspace already locked (lock ID: "ws-abc123")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.
Terraform was killed (Ctrl+C during apply, CI timeout, network drop) and didn't clean up the lock.
A Terraform Cloud run got stuck in "planning" or "applying" status and never completed.
Two merge events triggered parallel pipelines that both try to apply against the same workspace.
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 planVia the UI:
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"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"Use the lock ID from the error message:
# Force-unlock using the lock ID from the error
terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890Important: 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-ef1234567890If 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"}}'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-approveTerraform Cloud automatically queues runs. If a run is already in progress, new runs wait. Configure this in workspace settings:
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 1Who: field in the lock info — is it a teammate or CI?resource_group (GitLab) or concurrency (GitHub Actions)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.
Fix terraform CloudWatch Log Group ResourceAlreadyExistsException. Import orphaned log groups, prevent Lambda auto-creation
Fix terraform import errors when a resource already exists in state. Covers state rm, state show, reimport workflow, import blocks
Fix terraform too many command line arguments errors. Correct -var syntax, quote values with spaces, and learn proper Terraform CLI argument format for plan
Fix terraform invalid escape sequence errors. Double backslashes for Windows paths, use heredocs for regex, and learn all valid HCL escape sequences.