Fixing Terraform DependencyViolation: Stop Stuck Resource Deletions

intermediate🏗️ Terraform2026-07-15| Terraform CLI (All versions), AWS/Azure/GCP Cloud Providers

Error Message

Error: Error deleting Security Group: DependencyViolation: The security group 'sg-0a1b2c3d4e5f6g7h8' has dependencies and cannot be deleted.
#terraform#aws#devops#troubleshooting

The 2 AM Production Stall

You’re decommissioning a staging environment, expecting a quick finish. Instead, Terraform hangs for 180 seconds before crashing with a DependencyViolation error. The terminal output confirms your nightmare: a Security Group or Subnet is stuck because something else is still using it.

Error: Error deleting Security Group: DependencyViolation: The security group 'sg-0a1b2c3d4e5f6g7h8' has dependencies and cannot be deleted.
    status code: 400, request id: 12345678-90ab-cdef-1234-567890abcdef

The problem stems from a rift between Terraform’s state file and the reality of your cloud console. Terraform believes it manages the resource lifecycle. However, an external entity—like a manually attached Network Interface (ENI), a lingering Lambda function, or a Load Balancer—is still clinging to that resource. The cloud API rejects the deletion request to prevent a network break.

Step 1: Identify the Ghost Dependency

Terraform tells you what it can't delete, but it’s notoriously tight-lipped about the "why." When an AWS Security Group is stuck, a hidden Network Interface (ENI) is almost always the culprit. This frequently happens with Elastic Load Balancers (ELB) or EKS nodes that don't spin down instantly.

Use the AWS CLI to find the specific attachment holding your resource hostage:

# Find ENIs linked to the stuck Security Group
aws ec2 describe-network-interfaces \
    --filters Name=group-id,Values=sg-0a1b2c3d4e5f6g7h8 \
    --query 'NetworkInterfaces[*].{ID:NetworkInterfaceId,Description:Description,Status:Status}'

If this command returns an active ID, you've found your ghost. AWS Lambda ENIs are famous for this. Even after a function is deleted, the network interface can linger for several minutes before the VPC reclaims it. Terraform cannot see these manual or system-managed attachments because they aren't in your .tfstate file.

Step 2: Practical Solutions

Method A: The Lifecycle Fix (Proactive)

During an apply, Terraform often tries to delete an old resource before creating its replacement. If other resources still point to the old ID, the process fails. You can reverse this logic with a lifecycle block.

resource "aws_security_group" "web_server" {
  name = "web-sg-v2"

  lifecycle {
    create_before_destroy = true
  }
}

This block forces Terraform to provision the new Security Group first. It then updates all dependent resources to point to the new ID. Only after the old resource is truly "alone" does Terraform attempt to delete it. This is the most effective way to prevent dependency loops in networking code.

Method B: Manual Intervention (Reactive)

Sometimes you just need to clear the path manually. This is common when a Kubernetes cluster (EKS) creates Load Balancers that Terraform doesn't track.

  • Locate and Detach: Use the ID from Step 1 to find the resource in the AWS Console. Manually delete the ENI or Load Balancer.
  • Sync the State: Once the obstacle is gone, run terraform plan -refresh-only to update your local state.
  • Finish the Job: Re-run terraform destroy. It should now pass instantly.

Method C: Forcing the State to Forget

If you have already deleted the resource via the cloud console but Terraform keeps trying to destroy it, you need to perform a "lobotomy" on your state file. Use the state rm command.

terraform state rm aws_security_group.web_server

Note: This only removes the tracking record. It does not delete actual infrastructure. Use this only when the physical resource is already gone or if you intend to manage it manually.

Step 3: Verification

Confirm the fix by running a plan. If you implemented create_before_destroy, the output should look like this:

# aws_security_group.web_server must be replaced
# +/- create replacement and then destroy

A successful run will end with the satisfying Destroy complete! Resources: 1 destroyed. message.

Lessons for the Future

  • Avoid "ClickOps": Manually attaching a Security Group to an EC2 instance in the console is a guaranteed way to break your next Terraform run.
  • Account for Lambda Latency: VPC-enabled Lambdas can take up to 20 minutes to fully release ENIs in rare cases. Build retry logic into your CI/CD pipelines if this happens often.
  • Standardize Lifecycle Rules: Apply create_before_destroy to foundational components like Subnets and VPC Peering connections by default.
  • Watch for Silent Killers: NAT Gateways and VPC Endpoints often have long cleanup times that block parent VPC deletions.

Related Error Notes