Omni FinOps

Idle and orphaned cloud resource cleanup for engineering teams on AWS and Google Cloud

A checklist of idle and orphaned cloud resources that bill for nothing on AWS and Google Cloud, from unattached EBS volumes to forgotten IPs, and how to clean them up safely.

Mateo VargasUpdated · 9 min read
Contents
  1. How orphaned cloud resources happen
  2. The idle cloud resources checklist
  3. Unattached EBS volumes and persistent disks
  4. Load balancers and NAT gateways nobody uses
  5. Read-only commands to list orphaned cloud resources
  6. A safe cleanup process
  7. Keep idle resources from coming back
  8. How Omni FinOps finds them
  9. Frequently asked questions

In short

  • Idle resources run but do no work; orphaned ones were left behind when their parent was deleted, and both bill every hour.
  • An idle Application Load Balancer costs $0.0225 an hour and an idle NAT gateway $0.045 an hour in us-east-1, before usage charges.
  • AWS Compute Optimizer and Google Cloud’s recommender flag idle resources for free, but neither deletes anything.

Idle cloud resources are the easiest money on any AWS or Google Cloud bill. Nobody uses them, nothing depends on them, and they bill every hour anyway. This post is a cleanup checklist for engineering teams on AWS and Google Cloud: what to look for, the read-only commands that list it, and a process for deleting it without deleting something a teammate still needs.

Two kinds of waste hide under the same name. An idle resource is still running but does no work, like an instance at 2% CPU for a month. An orphaned resource is something left behind when its parent went away, like a volume whose instance was terminated. Both are on the bill. Orphans are worse, because nothing on a dashboard of running services points at them.

How orphaned cloud resources happen

Nobody creates waste on purpose. It falls out of normal work:

  • Terminating an instance keeps its extra volumes. Root volumes are deleted with the instance by default, but volumes you attach after launch keep DeleteOnTermination off. They survive and keep billing for every GiB.
  • Deregistering an AMI keeps its snapshots. By default, deregistering an AMI has no effect on its snapshots, so the storage keeps billing after the image is gone.
  • Addresses outlive their servers. An Elastic IP or a reserved static IP stays allocated to your account until someone releases it.
  • Services get retired, their plumbing doesn’t. A load balancer, a NAT gateway or a staging VPC built for one project often outlives the project.
  • Experiments and people move on. A proof of concept launched by hand, outside your infrastructure as code, has no owner the day its author changes teams.

That last one is why cleanup is slow. Finding an orphan takes one command. Finding out whether anyone still wants it takes a conversation.

The idle cloud resources checklist

These are the usual suspects, the point where each one counts as waste, and what it costs while it sits there. Prices are us-east-1 and us-central1 list prices as of October 2026.

Resource Waste when Cost while idle Details
EC2 instances, Compute Engine VMs CPU under 5% every day for two weeks The full instance price Idle instances
Dev, test and staging instances Running 24/7 64% of compute is nights and weekends Scheduling
EBS volumes, persistent disks Attached to no instance $0.08 per GB-month for gp3 (EBS pricing) Unattached volumes
Snapshots Older than 180 days and backing no image Up to $0.05 per GB-month Old snapshots
AMIs and custom images No instance or launch template uses them Their snapshots’ storage Unused AMIs
Public IPv4 addresses Reserved, attached to nothing $0.005 per hour on AWS, about $3.65 a month (VPC pricing) Unused IPs
Load balancers No requests for two weeks $0.0225 per hour for an ALB, about $16 a month before capacity units (ELB pricing) Below
NAT gateways No traffic for two weeks $0.045 per hour, about $33 a month before data (VPC pricing) Below

On Google Cloud, a reserved static IP that sits unused costs $0.01 per hour, twice the price of one in use (network pricing). A stopped VM stops billing for compute, but its disks and any static IP keep billing on both clouds.

Each row alone looks small. Together, across every Region and project nobody checks, they add up, and they never stop on their own.

Unattached EBS volumes and persistent disks

Unattached EBS volumes are the most common orphan on AWS. A volume in the available state is attached to nothing and still bills for its full provisioned size, not the data on it. On Google Cloud, the same thing is a persistent disk with no users.

The fix is short: check who owns it, snapshot it if anyone might need the data, then delete it. The unattached EBS volumes guide has the commands for every Region, the snapshot-then-delete sequence, and how to stop new orphans with DeleteOnTermination.

Load balancers and NAT gateways nobody uses

These two don’t show up in an instance list, so they’re easy to miss. Both bill by the hour whether or not traffic flows through them.

For an Application Load Balancer, total RequestCount over 14 days. The dimension value is the part of the load balancer’s ARN after loadbalancer/:

aws cloudwatch get-metric-statistics \
  --namespace AWS/ApplicationELB \
  --metric-name RequestCount \
  --dimensions Name=LoadBalancer,Value=app/old-api/50dc6c495c0c9188 \
  --start-time 2026-09-28T00:00:00Z \
  --end-time 2026-10-12T00:00:00Z \
  --period 1209600 \
  --statistics Sum

No datapoints, or a sum of zero, means nothing has called it in two weeks. For a NAT gateway, list the ones that exist, then check BytesOutToDestination in the AWS/NATGateway namespace the same way, with Name=NatGatewayId:

aws ec2 describe-nat-gateways \
  --filter Name=state,Values=available \
  --query 'NatGateways[].[NatGatewayId, VpcId, SubnetId]' \
  --output table

AWS Compute Optimizer also flags idle NAT gateways: one that isn’t associated with any route table and had no connections or packets over its lookback period (AWS docs). Before deleting one, check whether it’s part of a disaster recovery setup. A standby gateway in a second Availability Zone can look idle and still be on purpose.

Read-only commands to list orphaned cloud resources

Everything below only reads. Run it with a read-only role. These run in one Region or project; the guides linked above loop over every Region.

On AWS:

# Volumes attached to nothing
aws ec2 describe-volumes --filters Name=status,Values=available \
  --query 'Volumes[].[VolumeId, Size, VolumeType, CreateTime]' --output table

# Elastic IPs attached to nothing
aws ec2 describe-addresses \
  --query 'Addresses[?AssociationId==null].[PublicIp, AllocationId]' --output table

# Stopped instances: their volumes and Elastic IPs still bill
aws ec2 describe-instances --filters Name=instance-state-name,Values=stopped \
  --query 'Reservations[].Instances[].[InstanceId, InstanceType, Tags[?Key==`Name`]|[0].Value]' \
  --output table

On Google Cloud:

# Disks with no VM using them
gcloud compute disks list --filter="-users:*" \
  --format="table(name, zone.basename(), sizeGb, type.basename())"

# Static IPs reserved but not in use
gcloud compute addresses list --filter="status=RESERVED" \
  --format="table(name, region.basename(), address)"

# Stopped VMs (Compute Engine calls them TERMINATED)
gcloud compute instances list --filter="status=TERMINATED" \
  --format="table(name, zone.basename(), machineType.basename())"

# Snapshots older than 180 days
gcloud compute snapshots list --filter="creationTimestamp<'2026-04-15'" \
  --format="table(name, diskSizeGb, storageBytes, creationTimestamp)"

Both clouds also recommend idle resources for free. AWS Compute Optimizer flags idle EC2 instances, EBS volumes, RDS databases and NAT gateways once you opt in; it calls an instance idle when peak CPU stays under 5% and network I/O under 5 MB a day for 14 days. Google Cloud’s recommender flags disks, custom images and external IPs that haven’t been attached to anything for 15 days (Google Cloud docs):

gcloud recommender recommendations list \
  --project=my-project \
  --location=us-central1-c \
  --recommender=google.compute.disk.IdleResourceRecommender \
  --format=yaml

Swap the recommender for google.compute.address.IdleResourceRecommender (with a Region as the location) or google.compute.image.IdleResourceRecommender (with global).

A safe cleanup process

The commands find candidates. This process keeps you from deleting the one disk someone’s restore plan depends on.

  1. Find the owner. Look at tags or labels (owner, team, env), the CloudTrail or Cloud Audit Logs entry that created it, and the infrastructure as code repo. If Terraform manages it, delete it there, or the next apply brings it back.
  2. Announce it. Post the list where the owners will see it, with a date. Silence until that date is an answer.
  3. Stop before you delete. For instances, stop them first and wait. A stopped instance is reversible in a minute; a terminated one isn’t.
  4. Keep a copy where it’s cheap. Snapshot a volume before deleting it. A snapshot stores only the data written, so it usually costs less than the provisioned volume.
  5. Wait, then delete. Two weeks covers most monthly jobs nobody remembered. Then delete, release or deregister.
  6. Write down what you removed. The resource ID, its snapshot ID and the date. When someone asks in March, you can answer in a minute.

Releasing an IP address is the one step you can’t undo: once released, that address is gone. Check DNS records for it first.

Keep idle resources from coming back

Cleanup is a one-off unless something changes upstream. A few habits do most of the work:

  • Tag at creation. An owner and an env tag on every resource turn “who owns this?” into a filter. Enforce it in your infrastructure as code, not by asking.
  • Delete data volumes with their instance. Set DeleteOnTermination in launch templates for volumes that hold nothing worth keeping, and the equivalent --auto-delete on Google Cloud disks.
  • Schedule non-production. Dev and staging that run weekdays from 08:00 to 20:00 bill 64% less for compute.
  • Look every week. Orphans are cheap one at a time, which is why they pile up. A weekly list catches them while the person who made them still remembers why.

How Omni FinOps finds them

Omni FinOps runs these checks on every refresh, read-only, for AWS and Google Cloud. It flags instances under 5% CPU every day for two weeks, volumes and disks attached to nothing, snapshots older than 180 days that back no image, unused AMIs and unused public IPs, and tagged dev or staging instances that run around the clock. Each lead shows its monthly saving and the exact AWS CLI or gcloud command for your team to run. It never deletes anything itself. See Omni FinOps for AWS and for Google Cloud for what it reads.

Frequently asked questions

What is the difference between idle and orphaned cloud resources?

An idle resource still runs but does no useful work, like an instance whose CPU stays under 5% for weeks. An orphaned resource was left behind when the thing it belonged to was deleted, like a volume from a terminated instance or snapshots from a deregistered AMI. Both bill every month. Orphans are harder to spot because nothing running points at them, so they rarely show up on a service dashboard.

Do stopped instances still cost money?

Yes, partly. A stopped EC2 instance or Compute Engine VM stops billing for compute, but its volumes or persistent disks keep billing for their full size, and so does any Elastic IP or static IP attached to it. An instance stopped for months is usually waste twice over: you pay for its storage, and nobody remembers what it was for. Snapshot or image it, then terminate it.

Is it safe to delete unattached EBS volumes?

Usually, once you know nobody needs the data. A volume in the available state is attached to nothing, so deleting it can’t break a running instance. The risk is losing data someone wanted. Check the owner tag and the CloudTrail entry that created it, and take a snapshot first if you’re unsure. A snapshot stores only the data written to the volume, so it usually costs less to keep.

How often should a team clean up idle cloud resources?

Look every week and delete on a schedule. A weekly list takes a few minutes and catches orphans while the person who created them still remembers why. Actual deletion can follow a two-week waiting period after you announce the list, which covers most monthly jobs. Quarterly cleanups tend to find months of waste at once and far more resources nobody can explain.

Can AWS or Google Cloud find idle resources for me?

Both have free recommenders. AWS Compute Optimizer flags idle EC2 instances, EBS volumes, RDS databases, NAT gateways and more once you opt in, using a 14-day lookback for most resources. Google Cloud’s recommender flags idle VMs, plus disks, custom images and external IPs unattached for 15 days. Neither one deletes anything, and each covers only its own cloud, so someone still has to review the list and act on it.

Why do idle load balancers and NAT gateways cost so much?

They bill by the hour whether or not any traffic flows. As of October 2026 in us-east-1, an Application Load Balancer costs $0.0225 an hour and a NAT gateway $0.045 an hour, before any usage charges. That’s roughly $16 and $33 a month each for doing nothing. They also don’t appear in an instance list, so they outlive the services they were built for.

Mateo Vargas writes Omni FinOps’ cost guides, from the same rules the product runs on every refresh. Prices and product facts are as of October 12, 2026; check each provider’s pricing page.

More from the blog