Fix Terraform Destroy DependencyViolation on AWS VPC

Tested on Ubuntu 26.04 LTS (Resolute Raccoon)
Package terraform 1.16.1
hashicorp/aws 6.63.0
awscli 2.36.38
Applies to Any host with Terraform and the AWS provider; examples use ap-south-1
Privilege AWS identity that can describe and delete VPC resources; normal user on the workstation
Scope Fix DependencyViolation: The vpc 'vpc-…' has dependencies and cannot be deleted during terraform destroy — find real AWS blockers with the CLI, recover when child resources were removed from state with terraform state rm, re-import with terraform import, and destroy in dependency order. Does not cover EKS or managed-service ENI delays, multi-account networking, or replacing a VPC in place.
Short on time?Beta Get the gist of this 10-minute read in seconds, then ask follow-up questions

terraform destroy on an AWS VPC stack can fail near the end with a message that sounds vague:

text
Error: deleting EC2 VPC (vpc-03cbdc25f0ea9da5f): operation error EC2: DeleteVpc, …
api error DependencyViolation: The vpc 'vpc-03cbdc25f0ea9da5f' has dependencies and cannot be deleted.

In plain terms, AWS will not delete the VPC until everything inside it is gone first.


What usually causes DependencyViolation

  • A subnet, instance, or network interface still exists in AWS
  • An internet gateway is still attached
  • Something was created in the console and never added to Terraform state
  • Child resources were removed from Terraform state with terraform state rm, but the real AWS objects were left running

Terraform only destroys what it tracks in state. When state and AWS do not match, destroy may try the VPC while children still exist in your account.

This guide shows how to read the error, list what AWS still holds inside the VPC, and recover with terraform import or manual teardown.

NOTE
You need working AWS credentials before any step here. If terraform plan fails with No valid credential sources found, fix auth first — see Fix Terraform AWS credentials error.

What the VPC DependencyViolation error means

DependencyViolation is an EC2 API response, not a Terraform bug. AWS runs DeleteVpc only after every dependent object is gone.

Common blockers inside the VPC:

  • EC2 instances and their primary ENIs
  • Subnets (while instances or ENIs are attached)
  • Internet gateway attachments
  • Non-default security groups that still exist in the VPC
  • Custom route tables (orphaned after manual subnet deletion)
  • Resources created in the console or by another tool that Terraform never tracked

An attached ENI can also prevent a security group itself from being deleted, even when no instance is running.

When destroy works normally: every child resource is still in state. terraform destroy deletes in dependency order — instance first, then subnet, gateway, security group, VPC.

When you see this error: Terraform tries to delete the VPC while AWS still has children that are not in the destroy plan.


When only the VPC remains in state

A frequent trigger: someone ran terraform state rm on child resources during a rushed cleanup. The real EC2 instance, subnet, and gateway stay in AWS, but state lists only aws_vpc.main (or your VPC resource address).

What that looks like in state:

If your state already matches the broken pattern — VPC only, children missing — skip to Find what still blocks the VPC. To confirm the failure mode, list what Terraform tracks from your project directory:

bash
terraform state list

Sample output:

output
data.aws_ami.ubuntu
data.aws_availability_zones.available
aws_instance.target
aws_internet_gateway.pentest
aws_route_table.public
aws_route_table_association.public
aws_security_group.target
aws_subnet.public
aws_vpc.pentest

Remove every managed child of the VPC from state — leave only the VPC resource and data sources. Example after state rm on a stack with one instance, subnet, gateway, route table, association, and security group:

bash
terraform state rm aws_instance.target aws_subnet.public aws_internet_gateway.pentest aws_route_table_association.public aws_route_table.public aws_security_group.target

Terraform confirms six removals. Verify only the VPC remains in state:

bash
terraform state list

Sample output:

output
data.aws_ami.ubuntu
data.aws_availability_zones.available
aws_vpc.pentest

Run destroy. Terraform now plans to delete only the VPC, while the EC2 instance, subnet, and gateway still exist in AWS:

bash
terraform destroy -auto-approve

The provider may retry VPC deletion for some time while AWS still reports dependencies, printing Still destroying... lines, then fails:

output
aws_vpc.pentest: Destroying... [id=vpc-03cbdc25f0ea9da5f]
aws_vpc.pentest: Still destroying... [id=vpc-03cbdc25f0ea9da5f, 01m30s elapsed]
╷
│ Error: deleting EC2 VPC (vpc-03cbdc25f0ea9da5f): operation error EC2: DeleteVpc, https response error StatusCode: 400, RequestID: 76edd633-02b0-48f6-9e32-bc499245102c, api error DependencyViolation: The vpc 'vpc-03cbdc25f0ea9da5f' has dependencies and cannot be deleted.
╵

The VPC row may still appear in state after the error. That is expected — AWS rejected the delete, so Terraform keeps tracking it.


Find what still blocks the VPC

Set shell variables from your stack so the inspection commands stay short. Use terraform output when outputs exist, or copy the VPC ID from the error line:

bash
VPC_ID=$(terraform output -raw vpc_id)
REGION=ap-south-1

List network interfaces in the VPC. An ENI means something still has a network attachment inside the VPC.

Check these columns in the output:

  • Instance — EC2 instance ID when the ENI belongs to a VM
  • Type — interface type (interface, efa, etc.)
  • RequesterManaged — True often means Lambda, ELB, ECS, or another AWS service owns the ENI
  • Desc — service name or purpose string from AWS
bash
aws ec2 describe-network-interfaces --filters Name=vpc-id,Values="$VPC_ID" --region "$REGION" --query 'NetworkInterfaces[].{Id:NetworkInterfaceId,Status:Status,Instance:Attachment.InstanceId,Type:InterfaceType,RequesterManaged:RequesterManaged,Desc:Description}' --output table

Sample output:

output
---------------------------------------------------------------------------------------------------------
|                                       DescribeNetworkInterfaces                                       |
+------+---------------------+---------+-------------------+--------------------------------+-----------+
| Desc |         Id          | Instance| RequesterManaged  |            Status              |   Type    |
+------+---------------------+---------+-------------------+--------------------------------+-----------+
|      |  eni-0a1b2c3d4e5f67890 |  i-04d68e0520c9d0aa5 |  False            |  in-use                        |  interface |
+------+---------------------+---------+-------------------+--------------------------------+-----------+

The instance ID in the table is the blocker Terraform no longer manages. Match it to resources you removed from state or created outside Terraform.

  • RequesterManaged: False + an instance ID → likely EC2
  • RequesterManaged: True or a service name in Desc → likely Lambda, ELB, ECS, or another managed service

Check subnets and the internet gateway next:

bash
aws ec2 describe-subnets --filters Name=vpc-id,Values="$VPC_ID" --region "$REGION" --query 'Subnets[].SubnetId' --output text

Sample output:

output
subnet-0b3be385af2728630

List internet gateways still attached to the VPC:

bash
aws ec2 describe-internet-gateways --filters Name=attachment.vpc-id,Values="$VPC_ID" --region "$REGION" --query 'InternetGateways[].InternetGatewayId' --output text

Sample output:

output
igw-0bf5307431a34eedc

If you use the AWS CLI regularly, run the same style of filter for security groups, route tables, and NAT gateways. Delete children before the VPC on every path.


Fix by re-importing children, then destroy

Use this path when the real AWS objects still match your .tf files.

Steps:

  1. terraform import each child resource back into state
  2. Confirm terraform state list shows the full stack again
  3. Run terraform destroy and read the plan before typing yes

Terraform can then delete children before the VPC, in the correct order.

Import the subnet:

bash
terraform import aws_subnet.public subnet-0b3be385af2728630

Import exits with Import successful! when the address resolves. Import the internet gateway:

bash
terraform import aws_internet_gateway.pentest igw-0bf5307431a34eedc

Import the security group:

bash
terraform import aws_security_group.target sg-0a2a6e26ae449e908

Import the public route table:

bash
terraform import aws_route_table.public rtb-0bbbaa5c3e76438d4

Route table associations use subnet-id/route-table-id as the import ID:

bash
terraform import aws_route_table_association.public subnet-0b3be385af2728630/rtb-0bbbaa5c3e76438d4

Import the target instance:

bash
terraform import aws_instance.target i-04d68e0520c9d0aa5

Confirm state again lists every managed resource:

bash
terraform state list

Sample output:

output
aws_instance.target
aws_internet_gateway.pentest
aws_route_table.public
aws_route_table_association.public
aws_security_group.target
aws_subnet.public
aws_vpc.pentest

Run a full destroy and read the plan before you confirm — Terraform should delete the instance and subnet before the VPC:

bash
terraform destroy

Type yes when the destroy plan lists every child resource. Sample output:

output
aws_instance.target: Destruction complete after 26s
aws_subnet.public: Destruction complete after 1s
aws_security_group.target: Destruction complete after 2s
aws_vpc.pentest: Destruction complete after 2s

Destroy complete! Resources: 7 destroyed.

That is the preferred fix when configuration still describes the real objects. Replace the IDs below with yours from describe-subnets, describe-internet-gateways, and describe-instances. For import syntax beyond this walkthrough, see Terraform import and Terraform state command.


Fix by deleting AWS dependencies manually

Use this path when import is not practical:

  • Configuration no longer matches what is in AWS
  • Resources were renamed or recreated outside Terraform
  • Console drift left stray objects in the VPC

Delete blockers in AWS first, then clear the VPC from state or retry destroy.

Terminate the target instance and wait until it disappears:

bash
aws ec2 terminate-instances --instance-ids i-04d68e0520c9d0aa5 --region ap-south-1

Wait until EC2 reports the instance as terminated:

bash
aws ec2 wait instance-terminated --instance-ids i-04d68e0520c9d0aa5 --region ap-south-1

wait exits silently when the instance reaches terminated. Detach and delete the internet gateway:

bash
aws ec2 detach-internet-gateway --internet-gateway-id igw-0bf5307431a34eedc --vpc-id vpc-03cbdc25f0ea9da5f --region ap-south-1

Delete the gateway after it is detached:

bash
aws ec2 delete-internet-gateway --internet-gateway-id igw-0bf5307431a34eedc --region ap-south-1

Delete the subnet and non-default security group:

bash
aws ec2 delete-subnet --subnet-id subnet-0b3be385af2728630 --region ap-south-1

Remove the security group once nothing references it:

bash
aws ec2 delete-security-group --group-id sg-0a2a6e26ae449e908 --region ap-south-1

If you deleted a subnet by hand earlier, an orphaned custom route table may remain — list and delete it before the VPC:

bash
aws ec2 describe-route-tables --filters Name=vpc-id,Values=vpc-03cbdc25f0ea9da5f --region ap-south-1 --query 'RouteTables[?Associations[0].Main!=`true`].RouteTableId' --output text

Delete any non-main route table ID the query returns:

bash
aws ec2 delete-route-table --route-table-id rtb-0bbbaa5c3e76438d4 --region ap-south-1

Finish with one of these:

  • terraform destroy — VPC still in state; review the plan, then type yes
  • aws ec2 delete-vpc + terraform state rm aws_vpc.pentest — abandoning the directory entirely

Manual teardown order (delete in this sequence):

  1. Parent services that create ENIs; terminate EC2 instances
  2. Wait until ENIs disappear from describe-network-interfaces
  3. NAT gateways, load balancers, and VPC endpoints (if present)
  4. Subnets (removes subnet–route-table associations), or disassociate custom route tables first if you keep the subnet
  5. Detach and delete internet or other gateways
  6. Non-default security groups
  7. Custom route tables
  8. VPC

Troubleshooting

Symptom Likely cause Fix
DependencyViolation on aws_vpc during destroy Child resources still in AWS but missing from state, or out-of-band objects in the VPC CLI dependency listing; terraform import children; or manual deletion in order above
Still destroying... on VPC for many minutes AWS still reports dependencies on DeleteVpc Stop the run; inspect ENIs and subnets; fix blockers before retrying
Destroy succeeds in state but VPC remains in console Partial destroy or state rm on VPC only Reconcile with terraform state list and aws ec2 describe-vpcs
Error persists after instance terminated Lambda, ECS, or ELB managed ENI not yet released Wait 10–20 minutes and retry; delete the parent service first
Normal full destroy failed without state rm Console-created resource in the same VPC Import or delete the stray object; tag VPC resources with Project for easier filtering

For stage-by-stage debugging beyond VPC teardown, use Terraform troubleshooting.


Summary

When every child resource stays in state, terraform destroy removes instances, subnets, and gateways before the VPC and usually completes without DependencyViolation.

You see this error when:

  • Terraform calls DeleteVpc while AWS still has subnets, ENIs, gateways, or security groups
  • Child resources were removed from state with terraform state rm but left running in AWS
  • Console drift or another tool created objects inside the VPC that Terraform never tracked

Recovery in short:

  1. Confirm with terraform state list and the VPC ID from the error or terraform output
  2. Find blockers with describe-network-interfaces and related CLI filters
  3. Re-import children and run a full destroy, or delete AWS dependencies manually in order

terraform state rm stops Terraform from tracking a resource. It does not delete the real AWS object. Tag VPC resources with a consistent key (for example Project) so CLI filters stay simple when you hunt for strays.


References


Frequently Asked Questions

Does a normal terraform destroy ever hit DependencyViolation on a fully managed stack?

Rarely, if every object was created by Terraform and nothing was removed from state.

  • Terraform deletes instances before subnets
  • Gateways detach before the VPC
  • You see this error more often after state rm, console drift, or resources another service created inside the VPC

Why does Terraform retry VPC deletion for several minutes?

The provider may retry while AWS still reports dependencies. You can see repeated Still destroying... lines before the run finally fails.

  • Do not wait and hope the retries succeed
  • Inspect ENIs and subnets, remove blockers, then retry destroy

Is terraform destroy -target=aws_vpc.main a shortcut?

No.

  • -target selects the VPC and what the VPC depends on
  • It does not select subnets, instances, or other resources that depend on the VPC
  • That leaves AWS dependencies in place and can cause DependencyViolation

Use a normal full destroy when state is accurate. Reserve -target for exceptional recovery work.

Can I delete the default security group before the VPC?

No. AWS deletes the default security group when the VPC is deleted.

  • Remove non-default security groups first
  • An attached ENI can block deletion of a non-default group even when no instance is running

What if I already ran state rm and lost the resource IDs?

Try these in order:

  • Filter by VPC ID and resource tags in the AWS CLI (--filters Name=tag:Project,Values=your-project)
  • Read terraform show from an older state backup file if you have one
Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)