Orphaned cloud resources are the infrastructure equivalent of a direct debit you forgot to cancel. Each one is small. Collectively, they are a meaningful percentage of many teams' cloud bills, and they grow silently because nobody owns the job of finding them.
We're not talking about obviously idle servers here. We're talking about the category of resources that exist but have been severed from the workload they were created for: EBS volumes that were never deleted when an EC2 instance was terminated, EBS snapshots taken for a backup rotation policy that was abandoned six months ago, Application Load Balancers with no active targets behind them, Elastic IPs allocated but unassigned, NAT Gateways attached to subnets with no active traffic, old AMIs that are registered but unused.
Each of these has a per-hour or per-GB cost. None of them show up on a cost dashboard labeled "orphaned." They're folded into EC2-Other, or Storage, or Networking line items, invisible unless you cross-reference the billing data against the live resource inventory.
How orphaned resources accumulate
The lifecycle pattern is predictable. An engineer spins up a dev environment to test something. The EC2 instance gets terminated when the task is done. The EBS volume that was attached doesn't get deleted automatically (by default, EC2 instances delete the root volume on termination, but not additional volumes). It sits there. If the engineer provisioned it at 500 GB because that's the size they needed for the test data, it's now costing roughly $50/month for nothing.
Blue-green deployments are another common source. You bring up the green environment, cut traffic over, validate, and then... the blue environment lingers. Especially if the switch happened during a high-pressure release window and the cleanup was pushed to "later." The load balancer that fronted the blue environment stays allocated. The snapshots taken before the cutover stay around.
Snapshot policy drift is a specific category worth naming. Teams set up automated snapshots for compliance or disaster recovery, then those policies get changed, disabled, or the resource itself gets migrated. The old snapshots accumulate because nothing in the default AWS tooling expires them. After 18 months of a broken backup policy, the snapshot backlog can represent thousands of dollars in storage that nobody would choose to pay for if they saw the line item labeled clearly.
What a real inventory looks like
Let's put rough AWS pricing numbers on a typical orphaned-resource profile for a team managing around $400K/year in cloud spend.
| Resource type | Typical orphaned count | Unit cost | Est. monthly waste |
|---|---|---|---|
| Unattached EBS gp3 volumes | 8-15 volumes, avg 200 GB each | $0.08/GB-month | $128-$240 |
| Unused EBS snapshots | 40-200 snapshots, avg 50 GB each | $0.05/GB-month | $100-$500 |
| Idle Application Load Balancers | 2-4 ALBs | $16-22/month base | $32-$88 |
| Unassigned Elastic IPs | 3-8 EIPs | $3.65/month each | $11-$29 |
| Idle NAT Gateways | 1-3 gateways | $32-45/month each | $32-$135 |
Total: roughly $300-$1,000/month in orphaned resource waste for a mid-range cloud account. Annualized, that's $3,600-$12,000 that's not funding anything.
These aren't worst-case numbers. They're the range we've seen across accounts that had never been audited for orphaned resources before connecting to Finopscraft. The accounts with more developer autonomy and less infrastructure governance tended toward the upper end of the range.
Why this is hard to catch manually
The fundamental problem is that the billing API and the resource inventory API are two separate data sources. AWS Cost Explorer shows you spend by service. The EC2, RDS, and VPC describe APIs show you what exists. To identify an orphaned resource, you need to match a spend line to a resource, then check whether that resource has any active association (attached instance, active targets, traffic passing through it).
This is not a hard query to write. It's a tedious one. You need to call DescribeVolumes, filter by State=available (unattached), pull the VolumeId, match to billing by the resource ID tag. Do this for each orphan category across each account and each region. A well-run team with a single AWS account in us-east-1 can do this manually in an afternoon. A team with 3 accounts across 6 regions has 18 combinations to check, for every resource category, monthly. It stops happening consistently.
We're not saying manual audits are impossible. We're saying manual audits don't scale as the account count grows, and the month-over-month discipline required to keep the orphan list current competes with every other priority engineering teams have. The math on automating this is easy.
The detection logic we use
Finopscraft's waste detection runs against your connected AWS accounts on a configurable schedule (daily by default). The detection rules for orphaned resources follow this logic:
- EBS volumes: State = available AND created more than 7 days ago. (The 7-day grace period avoids false positives on volumes that are intentionally detached between use.)
- EBS snapshots: No active AMI registration, parent volume no longer exists, older than 30 days. We also flag snapshot chains where the parent policy appears to have stopped creating new snapshots (suggesting policy drift rather than active use).
- ALBs: No healthy targets in any target group for more than 72 hours. This handles blue-green windows where the ALB is technically active but fronting nothing.
- Elastic IPs: Not associated with a running instance or network interface. AWS charges for unassociated EIPs already, so this is a direct billing signal.
- NAT Gateways: Less than 1 MB of processed bytes in the trailing 7 days. Zero-traffic NAT Gateways are almost always forgotten infrastructure.
Each flagged resource gets a cost estimate (monthly and annualized), the date it was created, and any tags it carries (owner tag is especially useful for routing the cleanup task to the right person).
What to do with orphan flags
Automated detection produces a list. The list needs a human to act on it, which is where most waste-detection workflows stall. We route orphan flags as Jira tickets or Slack messages to the team or individual identified by the owner tag. If there's no owner tag, it routes to the infrastructure lead as a batch weekly digest.
The ticket text matters. "You have an unattached EBS volume vol-0abc123, 300 GB, costing $24/month, created 2026-01-15" is actionable. "Storage costs are high" is not. Specificity is what turns a waste detection flag into a deletion event.
We also track remediation rate, because unactioned flags are just noise. If a resource is flagged and nobody acts in 14 days, it escalates. If the escalation is also ignored, it gets marked as acknowledged (some orphaned resources are intentionally retained for compliance or audit reasons). The distinction between "genuinely orphaned" and "intentionally retained" is important to maintain so the list stays credible.
A note on snapshot costs specifically
EBS snapshot costs have a compounding effect that makes them worth calling out separately. Snapshots are incremental, so the cost per snapshot is low. But snapshot chains don't get cleaned up automatically, and AWS charges for every snapshot in the chain, not just the most recent. A team that's been running automated daily snapshots on a 1 TB production database for 18 months, with a retention policy that was supposed to expire after 30 days but stopped working due to a misconfigured lifecycle rule, can accumulate several thousand dollars in snapshot storage before anyone notices. We've seen this pattern more than once.
The fix isn't complicated once the problem is identified: set a data lifecycle manager rule on the EBS volume, delete the accumulated chain, and put a monitoring alert on snapshot storage growth that triggers if it exceeds a threshold based on your expected retention policy. But nobody does it until they find the problem, and the problem doesn't surface without cross-referencing inventory against billing.
The cost of running orphaned resources is real and recurring. It's also almost entirely avoidable with systematic detection. The time you spend setting up detection once pays back every month it runs.