The Shadow Cloud Spend: $50k a Month Hiding in Forgotten Dev Accounts
Every mid-size engineering organization has 5 to 15 AWS accounts that nobody actively owns. The “POC” account from 2024. The “team-old-name” account that survived the 2025 reorg. The “consultant-X-temp” account that got a working IAM role and then went quiet. Each one runs a few NAT gateways, an idle RDS, EBS volumes from 2023, and a CloudWatch log group with infinite retention.
Shadow cloud spend in those forgotten accounts totals $30,000 to $80,000 per month per organization. None of it produces business value. None of it shows up cleanly on the FinOps dashboard, because cost-allocation tag schemes assume the account has an active owner and the forgotten accounts have stale or no tags.
The accounts are hard to see for the same reason they are hard to reclaim. The original owner left. The team got renamed. The IAM root credentials are in a stale 1Password vault. AWS Organizations sees the account but no human has logged in for 9 months. So the cleanup gets postponed forever, and the bill compounds.
This post is the playbook. Inventory the accounts you forgot you have. Audit what is running in each. Decide per-account: archive, delete, adopt, or invoice. Then the structural fix: an account lifecycle policy that prevents the next round of orphans. The pattern composes with closed-loop FinOps and the cost-per-customer attribution work that gives you visibility on the bill in the first place.
The 5-15 unowned-account pattern
Talk to FinOps practitioners at any organization with more than a year of AWS history and 200+ engineers. The same archetype emerges every time. Four shapes account for most forgotten accounts.
| Archetype | Typical resources running | Monthly spend |
|---|---|---|
| The 2024 POC account (project never moved to prod) | 2 NAT gateways, 1 t3.large EC2, RDS db.t3.medium, 200 GB EBS | $4,200 |
| The renamed-team account (org chart changed, account name did not) | 1 NAT gateway, 6 idle EBS volumes, S3 bucket with versioning enabled, Lambda hourly cron with no purpose | $1,800 |
| The consultant temp account (engagement ended, account stayed) | 1 NAT gateway, 1 RDS Multi-AZ, EFS volume with 80 GB stale data | $3,600 |
| The disaster-recovery test account (test happened, account stayed) | Cross-region replication still running, 4 EBS snapshots from 2024, 1 forgotten Elastic IP | $2,400 |
Mid-size orgs typically have 8 to 12 accounts matching one of these archetypes. Total shadow spend lands $30,000 to $80,000 per month. At the upper end, this is roughly 5 percent of total cloud spend for an organization in the $1M to $2M monthly range, all of it producing zero business value. The pareto is heavy: of 10 unowned accounts, 2 typically account for 60 to 80 percent of the total shadow bill. Triage them first.
What is actually running in there
The audit checklist for an unowned account is short. Five resource types account for almost all the persistent cost.
| Resource | Typical monthly cost | Why it persists |
|---|---|---|
| NAT Gateway (idle) | $32 fixed + small data fees | Single biggest forgotten-account driver; runs even when no real traffic flows |
| RDS instance (idle, Multi-AZ) | $50 to $300 | RDS does not scale to zero; idle RDS bills the same as busy RDS |
| EBS volume (unattached) | $0.08 per GB-month | Unattached EBS bills indefinitely; 1 TB volume costs $80 per month for nothing |
| Elastic IPs (unattached) | $3.65 per EIP per month | AWS charges for unused EIPs; 4 forgotten EIPs is ~$15 per month per account |
| CloudWatch log groups (infinite retention) | $0.03 per GB-month + ingest | Default retention is “Never expire”; 200 GB of logs costs $6 per month, growing forever |
NAT gateways are the single biggest driver. A forgotten NAT in a quiet account costs $40 to $100 per month because there is still small egress from CloudWatch agent telemetry, S3 health checks, or stale Lambda functions logging to a CloudWatch log group. The fixed $32 per month is the floor; the data processing on stale background traffic adds another $10 to $70 depending on what tooling was left running.
Why your dashboards miss them
Cost dashboards are designed for active accounts. Tag-based allocation, business-unit roll-ups, monthly budget comparisons. The whole machinery assumes the account has an owner who tagged resources and watches the budget. Forgotten accounts break every assumption.
| Dashboard view | What it shows | Why orphans hide |
|---|---|---|
| Cost by tag | Spend grouped by team or project | Forgotten accounts have stale tags; resources aggregate to “untagged” or “unallocated” |
| Cost by business unit | Roll-up to org chart | The owning business unit no longer exists in the org chart; spend has nowhere to roll up |
| Account-level monthly | Per-account totals | Visible but requires manual filtering; small accounts get ignored |
| Cost anomaly alerts | Sudden changes in spend | Forgotten accounts have stable spend curves; no anomaly fires |
The “untagged” and “unallocated” buckets are the tell. They contain real money but get scrolled past during cost reviews because the conversation is always about active teams. Until someone deliberately filters to “spend in accounts with no owner-tag,” the shadow stays shadow.
The triage playbook
The cleanup is a one-time exercise. The structural fix (lifecycle policy) prevents the next round. Both are needed; do the cleanup first to free the budget, then ship the policy to keep it free.
1. Inventory unowned accounts. Run the AWS Organizations API for the full account list. Cross-reference against the owner-team tag. Anything missing the tag, or with a tag pointing to a team that no longer exists in HR, is a candidate.
2. CloudTrail lookup for original owner. The first IAM principal to provision resources in the account is usually the architect. Search CloudTrail for the earliest iam:CreateUser or ec2:RunInstances event. The principal email resolves to a team-of-record via your identity provider. Even if that engineer left, their team usually still exists.
3. Apply lifecycle:orphan tag. This is the lowest-risk first action. The tag declares the account orphaned without changing any resource state. It also gives the cost dashboard something to filter on so the spend stops hiding.
4. Attach Deny-all SCP. Use AWS Organizations Service Control Policies to prevent new resources from being created in tagged-orphan accounts. The SCP is reversible. Existing resources keep running until the per-account decision is made, but no new spend starts.
5. Per-account decision. Triage each account in 15 to 30 minutes once tooling is in place. List resources, check the top 3 cost drivers, contact the most plausible owner, decide.
| Triage outcome | When to choose | Key risk |
|---|---|---|
| Archive (suspend the account) | Most cases; preserves audit trail and rollback path | Suspended accounts cost ~$0 but stay in your Organization |
| Adopt (new owner team takes over) | Active workload found that someone still uses | Owner accepts the budget and the operational responsibility |
| Delete (close the account) | Confirmed nothing of value, no audit-retention need | Closure is permanent after 90 days; cannot recover anything |
| Invoice the team | Workload exists, owner found, payment dispute | Sets expectation that team owns spend going forward |
For 10 unowned accounts, total triage effort is 3 to 5 hours over 2 to 3 days. The ROI ratio is heavy: a few engineering hours against $30k to $80k per month in recovered spend.
The lifecycle policy that prevents the next round
The cleanup is a one-time saving. The lifecycle policy is the structural saving. Without policy, orphans regenerate at roughly 1 to 3 new accounts per quarter as teams reorg and projects end.
The policy has three rules. First, every AWS account in the Organization must have a OwnerTeam tag and a RenewalDate tag. Resources cannot be created in an account that lacks both, enforced by SCP. Second, on the renewal date each year, the platform team validates that the OwnerTeam still exists in HR and the team lead acknowledges renewal. Third, accounts that fail revalidation get auto-tagged lifecycle:orphan, which triggers the Deny-all SCP, which halts new spend within 24 hours.
The annual cron job is the low-effort win. It runs once a year per account, sends an email to the OwnerTeam asking for renewal, and auto-tags orphan if no acknowledgment lands within 14 days. Running this for the first time on an existing fleet finds the orphans you already have. Running it forward keeps the fleet clean.
Closing the loop
The whole pattern is a closed-loop FinOps instance. Detect: no-owner-tag at renewal. Decide: auto-tag orphan. Act: attach Deny-all SCP. Verify: no new resources created in the account for 30 days. Same shape as cost anomaly response and IAM remediation, just on an annual cadence instead of 5-minute.
This composes naturally with policy-aware AI cloud governance. The policy graph already knows which teams exist, which accounts they own, and which exceptions are valid. Adding “is this account orphan” is one more rule. The detection runs against the same governance state that powers the rest of the autonomous-cloud loop.
The shadow cloud spend is not a vendor problem or a tooling problem. It is an organizational lifecycle problem with a tooling-shaped fix. AWS Organizations + tag enforcement + annual revalidation + Deny SCPs are all native primitives. The structural cost saving is in the policy, not the cleanup, because the cleanup happens once and the policy holds the line every year after.