Back to blog
AWSBest PracticesCloud SecurityCost OptimizationNetworking

Unused Elastic Network Interfaces in AWS: Why They Matter and How to Clean Them Up

Learn what unused AWS Elastic Network Interfaces are, why orphaned ENIs waste IPs and hide costs, and how to find, delete, and prevent them with CLI and IaC.

TL;DR

This check flags Elastic Network Interfaces (ENIs) in the available state, meaning they aren't attached to any instance. They clutter your account, can keep paired resources like Elastic IPs billing, and sometimes signal a half-finished or orphaned deployment. Delete the ones you confirm are unused with aws ec2 delete-network-interface.

An Elastic Network Interface is the virtual NIC that connects an EC2 instance, Lambda function, or other AWS service to a VPC. Most of the time you never touch them directly because AWS creates and attaches them automatically when you launch an instance. But ENIs can also exist on their own, detached from everything, sitting in the available state. The Lensix Unused Elastic Network Interface check (ec2_unusedeni) finds exactly these.

On their own, an idle ENI isn't a screaming emergency. But they pile up, they hide cost, and they are often the breadcrumb trail left behind by deleted instances, broken Terraform applies, or load balancers that didn't tear down cleanly. Cleaning them up keeps your network inventory honest.


What this check detects

The check queries your EC2 network interfaces and reports any with a status of available. That status means the ENI is provisioned in a subnet, consuming an IP address, but is not attached to any compute resource.

You can reproduce what Lensix sees with a single CLI call:

aws ec2 describe-network-interfaces \
  --filters "Name=status,Values=available" \
  --query 'NetworkInterfaces[].{ID:NetworkInterfaceId,Subnet:SubnetId,IP:PrivateIpAddress,Desc:Description}' \
  --output table

Note: An ENI in the available state is genuinely detached. This is different from in-use (attached to a running or stopped instance) and attaching/detaching (mid-transition). Only available ENIs are truly orphaned.

Where unused ENIs come from

  • Terminated instances where the ENI was created separately and not set to delete on termination.
  • Deleted load balancers, NAT gateways, or VPC endpoints that left their interfaces behind.
  • Lambda functions in a VPC that have scaled down. AWS may leave Hyperplane ENIs in available for a while before reaping them.
  • Failed or interrupted IaC applies that created the interface but never attached it.
  • Manual experiments where someone created a secondary ENI and forgot about it.

Why it matters

An unused ENI feels harmless, and in isolation it mostly is. The risk comes from what it represents and what it drags along with it.

1. It consumes IP addresses

Every ENI holds at least one private IP from its subnet. In tightly sized subnets, a handful of orphaned ENIs can exhaust your available address space and cause new instance launches or autoscaling events to fail with InsufficientFreeAddressesInSubnet. This is one of the more frustrating failures to debug because the subnet looks "empty" in the console while ENIs quietly hold the addresses.

2. It can keep cost meters running

The ENI itself is free. The trap is what's associated with it. An Elastic IP attached to an unattached ENI is billed as an idle EIP. Orphaned ENIs are frequently the reason a deleted instance still leaves a charge on your bill.

Warning: Before deleting an ENI, check whether it has an associated Elastic IP. Deleting the ENI does not automatically release the EIP, and a released EIP cannot be recovered if someone else grabs it. Disassociate and release the EIP deliberately.

3. It muddies your security posture

Detached ENIs still carry security groups. When you audit "what can reach this database security group," an orphaned ENI referencing it adds noise and can hide the real attachment relationships. A clean inventory makes incident response and least-privilege reviews far faster.

4. It signals drift

An ENI that nobody created on purpose usually means something didn't clean up properly. Treating these as a signal, not just junk to delete, often surfaces a broken teardown process or a Terraform module that leaks resources.


How to fix it

The fix is straightforward, but verification first is what separates a safe cleanup from an outage.

Step 1: Inspect the ENI before touching it

aws ec2 describe-network-interfaces \
  --network-interface-ids eni-0123456789abcdef0 \
  --query 'NetworkInterfaces[0].{Status:Status,Desc:Description,Requester:RequesterId,Groups:Groups,Assoc:Association}'

Pay attention to two fields:

  • Description and RequesterId tell you who created the ENI. Descriptions like ELB app/my-alb/..., AWS Lambda VPC ENI, or RDSNetworkInterface mean an AWS service owns it. Don't delete those manually.
  • Association shows whether an Elastic IP is attached.

Note: If the RequesterManaged flag is true, the ENI belongs to a managed service (ELB, RDS, Lambda, etc.). The correct fix is to remove the parent resource, not the ENI. AWS will reap the interface on its own schedule, usually within minutes to a couple of hours.

Step 2: Release any associated Elastic IP

If the ENI has an EIP and you no longer need that address:

# Disassociate the EIP from the ENI
aws ec2 disassociate-address --association-id eipassoc-0123456789abcdef0

# Release it (irreversible)
aws ec2 release-address --allocation-id eipalloc-0123456789abcdef0

Step 3: Delete the ENI

Danger: Deleting an ENI is permanent. If it secretly belongs to a service you forgot about, you can break connectivity. Always confirm Status is available and RequesterManaged is false before deleting in production.

aws ec2 delete-network-interface \
  --network-interface-id eni-0123456789abcdef0

Console steps

  1. Open the EC2 console and go to Network & Security > Network Interfaces.
  2. Filter by Status: available.
  3. Select an interface and review its Description and Requester-managed columns.
  4. For self-managed, unattached interfaces, choose Actions > Delete.

Bulk cleanup of confirmed-safe ENIs

Once you've reviewed your inventory and want to clean up only the unmanaged, available interfaces:

aws ec2 describe-network-interfaces \
  --filters "Name=status,Values=available" \
  --query 'NetworkInterfaces[?RequesterManaged==`false`].NetworkInterfaceId' \
  --output text | tr '\t' '\n' | while read eni; do
    echo "Deleting $eni"
    aws ec2 delete-network-interface --network-interface-id "$eni"
done

Tip: Run the loop with the delete-network-interface line commented out first, so it prints the candidate IDs without acting. Eyeball that list before letting it delete anything.

Terraform

If an ENI was created by Terraform, remove it through Terraform rather than the CLI so state stays consistent. To stop instances from leaking ENIs on termination, set the deletion behavior on attachment:

resource "aws_network_interface" "app" {
  subnet_id       = aws_subnet.app.id
  security_groups = [aws_security_group.app.id]
}

resource "aws_network_interface_attachment" "app" {
  instance_id          = aws_instance.app.id
  network_interface_id = aws_network_interface.app.id
  device_index         = 1
}

For the primary interface on an instance, the delete_on_termination default is true, which is what you want. The leaks usually come from secondary interfaces created and managed separately.


How to prevent it from happening again

Manual cleanup is a treadmill. The goal is to make orphaned ENIs rare and short-lived.

Set delete-on-termination correctly

When you create secondary ENIs, decide their lifecycle up front. If an ENI should die with its instance, attach it with that flag rather than relying on cleanup later.

aws ec2 modify-network-interface-attribute \
  --network-interface-id eni-0123456789abcdef0 \
  --attachment AttachmentId=eni-attach-0123456789abcdef0,DeleteOnTermination=true

Scheduled cleanup with an age threshold

Run a scheduled Lambda that finds available, non-managed ENIs older than a threshold and either tags them for review or deletes them. The key guardrail is age: a brand-new available ENI might be mid-deployment, while one that's been idle for a week is almost certainly garbage.

import boto3
from datetime import datetime, timezone, timedelta

ec2 = boto3.client("ec2")

def handler(event, context):
    threshold = datetime.now(timezone.utc) - timedelta(days=7)
    enis = ec2.describe_network_interfaces(
        Filters=[{"Name": "status", "Values": ["available"]}]
    )["NetworkInterfaces"]

    for eni in enis:
        if eni.get("RequesterManaged"):
            continue
        # Tag for visibility; flip to delete once you trust the logic
        ec2.create_tags(
            Resources=[eni["NetworkInterfaceId"]],
            Tags=[{"Key": "lensix:unused-eni", "Value": "review"}],
        )

Tip: Start in tag-only mode. Let it run for a couple of weeks, confirm it never tags something live, then graduate it to deletion. This builds confidence without risking a production outage on day one.

Policy-as-code in CI/CD

Catch the leak at the source. Add an OPA/Conftest or Checkov gate that fails a Terraform plan when a standalone aws_network_interface is defined without a corresponding attachment or lifecycle policy.

package terraform.eni

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_network_interface"
  not has_attachment(resource.address)
  msg := sprintf("ENI %s has no attachment defined; it may orphan", [resource.address])
}

Continuous monitoring

Lensix runs ec2_unusedeni on every scan, so orphaned interfaces surface within hours instead of at the next quarterly audit. Pair the finding with an alert to your operations channel so cleanup becomes routine rather than a fire drill.


Best practices

  • Tag everything you create manually. A secondary ENI with an owner and purpose tag is trivial to audit. An untagged one is a mystery you'll be afraid to delete.
  • Treat available ENIs as drift, not litter. When one appears unexpectedly, ask why before deleting. The answer often reveals a broken teardown path worth fixing.
  • Right-size subnets with headroom. Orphaned ENIs hurt most when address space is tight. Give VPC subnets enough room that a few stragglers don't block launches.
  • Audit associated Elastic IPs separately. The ENI is free; the EIP attached to it may not be. Catch idle EIPs in the same sweep.
  • Never delete requester-managed ENIs by hand. Remove the parent service and let AWS reap the interface. Deleting it directly can fail or break the service.
  • Make cleanup boring. A scheduled, age-gated, tag-first job that runs forever beats a heroic once-a-year purge.

Unused ENIs are the kind of low-grade clutter that's easy to ignore until a subnet runs dry or a surprise EIP charge shows up. Keeping the available count at or near zero is a small habit that pays off in cleaner audits, predictable launches, and a network inventory you can actually trust.