This check flags VPCs with no subnets and no active network interfaces, which usually means a leftover or abandoned network. Audit the VPC, confirm nothing depends on it, then delete it to cut clutter and shrink your attack surface.
Empty VPCs pile up quietly. Someone spins one up for a proof of concept, a Terraform run gets half-applied, or a team migrates workloads to a new account and never cleans up the old network. Months later you are staring at a VPC list with a dozen entries and no idea which ones still matter.
The vpc_unused check finds VPCs that have no subnets and no active elastic network interfaces (ENIs). In practice that means nothing is running inside them. This post explains what the check looks at, why an unused VPC is worth cleaning up, and how to remove it safely without breaking something you forgot about.
What this check detects
A VPC is the logical network boundary for your AWS resources. For anything to actually run inside it, you need subnets, and for traffic to flow you need network interfaces attached to instances, load balancers, NAT gateways, Lambda functions, RDS instances, and so on.
The check reports a VPC as unused when both of these are true:
- No subnets exist within the VPC, or
- No active ENIs are present in the VPC's subnets.
An ENI is created for almost every networked resource in a VPC. If there are zero ENIs in any attached state, there is effectively nothing live in that network. That combination is a strong signal the VPC is abandoned.
Note: Every AWS account comes with a default VPC in each region. The check may flag a default VPC you never used. Default VPCs are commonly targeted in security baselines precisely because they exist whether you want them or not, so an unused one is still worth removing.
Why it matters
An empty VPC is not going to leak data on its own. The risk is more about hygiene, governance, and the slow accumulation of things nobody owns.
Attack surface and shadow infrastructure
Unused VPCs often come bundled with leftover resources that are reachable or risky: an internet gateway, route tables pointing at the open internet, an over-permissive default security group, or a peering connection to a network that still carries traffic. A VPC that looks empty can still hold a door open to something else.
Attackers and red teams love forgotten infrastructure because nobody is watching it. If an old peering connection links a dead VPC to a live one, that path is a candidate for lateral movement that your monitoring may not cover.
Audit and compliance noise
When you run a security audit, every VPC needs to be accounted for. Empty ones force your team to investigate networks that turn out to be nothing, which wastes time and erodes confidence in the inventory. Frameworks like CIS, SOC 2, and PCI all expect you to know what your network looks like and why each piece exists.
Quota and operational drag
AWS limits you to 5 VPCs per region by default. Abandoned VPCs eat into that quota, and the more of them you have, the harder it is to reason about your network during an incident. Clean inventory makes incident response faster.
Warning: An empty VPC by itself costs nothing, but resources sometimes attached to one (NAT gateways, VPC endpoints, Elastic IPs) do bill by the hour. Always check for those before assuming the VPC is free. The check focuses on ENIs and subnets, so a billed-but-idle NAT gateway in another account state could still be lurking nearby.
How to fix it
The fix is to confirm the VPC really is unused, then delete it. Do not skip the confirmation step. "No ENIs right now" does not always mean "safe to delete," because some resources reference a VPC without keeping an ENI active.
Step 1: Identify the VPC and confirm it is empty
List your VPCs and note which one was flagged:
aws ec2 describe-vpcs \
--query 'Vpcs[].{ID:VpcId,CIDR:CidrBlock,Default:IsDefault,Name:Tags[?Key==`Name`]|[0].Value}' \
--output table
Check for subnets:
aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=vpc-0abc123456789def0" \
--query 'Subnets[].SubnetId' --output text
Check for any network interfaces:
aws ec2 describe-network-interfaces \
--filters "Name=vpc-id,Values=vpc-0abc123456789def0" \
--query 'NetworkInterfaces[].{ENI:NetworkInterfaceId,Status:Status,Desc:Description}' \
--output table
If both return empty, the VPC is a strong deletion candidate.
Step 2: Check for hidden dependencies
A VPC can still be referenced by resources that will block deletion or that matter for other networks. Look for these before deleting:
# Peering connections
aws ec2 describe-vpc-peering-connections \
--filters "Name=requester-vpc-info.vpc-id,Values=vpc-0abc123456789def0"
# VPC endpoints
aws ec2 describe-vpc-endpoints \
--filters "Name=vpc-id,Values=vpc-0abc123456789def0"
# NAT gateways (these bill hourly)
aws ec2 describe-nat-gateways \
--filter "Name=vpc-id,Values=vpc-0abc123456789def0"
# Internet gateways
aws ec2 describe-internet-gateways \
--filters "Name=attachment.vpc-id,Values=vpc-0abc123456789def0"
# Transit gateway attachments
aws ec2 describe-transit-gateway-vpc-attachments \
--filters "Name=vpc-id,Values=vpc-0abc123456789def0"
Note: A VPC managed by Terraform or CloudFormation should be deleted through that tool, not the CLI or console. Deleting it manually leaves the IaC state thinking the VPC still exists, and your next apply will recreate it or fail. Check for stack ownership before touching anything.
Step 3: Delete the VPC
You cannot delete a VPC until its dependencies are removed. Detach and delete internet gateways, remove peering connections and endpoints, then delete the VPC itself.
Danger: Deleting a VPC is irreversible. If anything still references it (a forgotten peering route, a CIDR another team depends on), removal can break connectivity elsewhere. Confirm ownership and dependencies before running these commands, and prefer a change window for shared accounts.
# Detach and delete an internet gateway if present
aws ec2 detach-internet-gateway \
--internet-gateway-id igw-0abc123456789def0 \
--vpc-id vpc-0abc123456789def0
aws ec2 delete-internet-gateway \
--internet-gateway-id igw-0abc123456789def0
# Finally delete the VPC
aws ec2 delete-vpc --vpc-id vpc-0abc123456789def0
For a default VPC, the same delete call works once subnets and the internet gateway are removed:
aws ec2 delete-vpc --vpc-id vpc-0abc123456789def0
Tip: AWS does not give you a single command to wipe a default VPC and its components. The community tool aws-nuke and AWS's own delete-default-vpc helper scripts can handle the teardown across all regions at once, which is handy when you want default VPCs gone account-wide as a baseline.
How to prevent it from happening again
Empty VPCs are a symptom of infrastructure that gets created outside a clear lifecycle. Close that gap with provisioning discipline and automated checks.
Manage VPCs as code
When VPCs live in Terraform or CloudFormation, deletion is a deliberate, reviewed action and there is always an owner. Tag every VPC at creation:
{
"resource": {
"aws_vpc": {
"main": {
"cidr_block": "10.20.0.0/16",
"tags": {
"Name": "prod-app-vpc",
"Owner": "platform-team",
"Environment": "production",
"ManagedBy": "terraform"
}
}
}
}
}
Gate it in CI/CD with policy-as-code
Use OPA or Conftest to reject VPC definitions that lack ownership tags, so nothing untracked ever reaches an account:
# policy/vpc.rego
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_vpc"
not resource.change.after.tags.Owner
msg := sprintf("VPC %s must have an Owner tag", [resource.address])
}
terraform show -json plan.out | conftest test -
Schedule recurring sweeps
Run the vpc_unused check on a schedule rather than on demand. Lensix can flag VPCs that drift into an empty state and surface them in your next report, so cleanup becomes routine instead of a once-a-year archaeology project.
Tip: Pair the unused check with a required-tag policy. A VPC tagged with an Owner and a TTL tells your automation exactly who to ping and when it is safe to reclaim. Untagged plus empty is the fastest signal that something was orphaned.
Best practices
- Delete default VPCs you do not use. Make removing unused default VPCs in every region part of your account baseline.
- One VPC, one purpose. Avoid creating throwaway VPCs for experiments. Use a dedicated sandbox account with a clear teardown schedule instead.
- Tag at creation, never after. Owner, environment, and managed-by tags should be mandatory through policy, so every VPC is traceable from day one.
- Review peering and transit attachments regularly. A dead VPC connected to a live network is the dangerous kind of empty, not the harmless kind.
- Keep IaC and reality in sync. Periodically reconcile your Terraform state against deployed resources so you catch drift before it becomes orphaned infrastructure.
- Document why each VPC exists. If you cannot explain a VPC's purpose in one sentence, it probably should not be there.
An unused VPC is rarely an emergency, but it is a reliable indicator of how well your account is governed. Clean networks make audits faster, incidents calmer, and your overall footprint easier to defend. Confirm it is empty, confirm no one depends on it, and remove it.

