Cloud Waste Detection: Finding and Eliminating Idle Resources

Systematically identify and eliminate cloud waste — idle instances, unattached volumes, oversized databases, and forgotten environments. Covers detection patterns, automated cleanup, tagging hygiene, and building a waste-aware culture across engineering teams.

Cloud waste detection identifies and eliminates idle or underutilized cloud resources—such as unattached volumes, stopped instances, unused load balancers, and dormant databases—that consume money without delivering value. It matters when cloud spend grows faster than workload demand, and teams begin to notice recurring costs for resources that are never touched. This process is essential during onboarding of new services, after scaling spikes, and in the routine health checks of a mature cloud environment.

Identifying Idle Resources

Find idle resources by measuring utilization over time and comparing it against thresholds. A resource is idle when its usage remains below a baseline for a defined period. The baseline may be a percentage of capacity, an absolute metric, or a combination of both.

Detecting Idle Compute Instances

An EC2 instance is idle when CPU utilization is below 10% for at least 72 consecutive hours. Use the CloudWatch API to retrieve metrics and detect such instances.

curl \
  --header "Authorization: Bearer $AWS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "MetricDataQueries": [
      {
        "Id": "cpu",
        "MetricStat": {
          "Metric": {
            "Namespace": "AWS/EC2",
            "MetricName": "CPUUtilization",
            "Dimensions": [
              { "Name": "InstanceId", "Value": "i-1234567890abcdef0" }
            ]
          },
          "Period": 3600,
          "Stat": "Average"
        },
        "ReturnData": true
      }
    ],
    "StartTime": "2023-10-01T00:00:00Z",
    "EndTime": "2023-10-04T00:00:00Z"
  }' \
  https://monitoring.us-east-1.amazonaws.com

The response includes a Datapoints array. If the Average value is less than 10 for 72 or more consecutive data points, the instance qualifies as idle.

When fetching multiple instances, use a batched request:

curl \
  --header "Authorization: Bearer $AWS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "MetricDataQueries": [
      {
        "Id": "cpu",
        "MetricStat": {
          "Metric": {
            "Namespace": "AWS/EC2",
            "MetricName": "CPUUtilization",
            "Dimensions": [
              { "Name": "InstanceId", "Value": "i-1234567890abcdef0" }
            ]
          },
          "Period": 3600,
          "Stat": "Average"
        },
        "ReturnData": true
      },
      {
        "Id": "network",
        "MetricStat": {
          "Metric": {
            "Namespace": "AWS/EC2",
            "MetricName": "NetworkIn",
            "Dimensions": [
              { "Name": "InstanceId", "Value": "i-1234567890abcdef0" }
            ]
          },
          "Period": 3600,
          "Stat": "Sum"
        },
        "ReturnData": true
      }
    ],
    "StartTime": "2023-10-01T00:00:00Z",
    "EndTime": "2023-10-04T00:00:00Z"
  }' \
  https://monitoring.us-east-1.amazonaws.com

The Period of 3600 seconds (1 hour) ensures hourly granularity. A single DataPoint is missing if the instance is stopped during the interval. This causes a spike in NetworkIn but a flat CPUUtilization, leading to false positives.

Detecting Idle Storage

An EBS volume is idle if it has no I/O for 14 days. Use the GetMetricData API to retrieve ReadOps and WriteOps over time.

{
  "MetricDataQueries": [
    {
      "Id": "read",
      "MetricStat": {
        "Metric": {
          "Namespace": "AWS/EC2",
          "MetricName": "ReadOps",
          "Dimensions": [
            { "Name": "VolumeId", "Value": "vol-1234567890abcdef0" }
          ]
        },
        "Period": 86400,
        "Stat": "Sum"
      },
      "ReturnData": true
    },
    {
      "Id": "write",
      "MetricStat": {
        "Metric": {
          "Namespace": "AWS/EC2",
          "MetricName": "WriteOps",
          "Dimensions": [
            { "Name": "VolumeId", "Value": "vol-1234567890abcdef0" }
          ]
        },
        "Period": 86400,
        "Stat": "Sum"
      },
      "ReturnData": true
    }
  ],
  "StartTime": "2023-09-15T00:00:00Z",
  "EndTime": "2023-10-01T00:00:00Z"
}

If both ReadOps and WriteOps are zero for 14 consecutive days, the volume is idle. A volume may be attached but unused if it’s part of a backup pipeline that runs weekly. The Sum statistic captures total operations per day; a Sum of zero means no I/O.

Detecting Idle Databases

A PostgreSQL instance is idle when pg_stat_database reports tup_returned and tup_fetched at zero for 7 days.

SELECT datname, 
       (tup_returned + tup_fetched) AS total_rows,
       (EXTRACT(EPOCH FROM now() - last_autovacuum) / 86400) AS days_since_autovacuum
FROM pg_stat_database
WHERE (tup_returned + tup_fetched) = 0
  AND last_autovacuum < now() - interval '7 days';

The query returns all databases with no read or write activity. A database with tup_returned at zero is effectively idle. If tup_fetched is also zero, it has not been accessed since last autovacuum.

Detecting Idle Load Balancers

An ALB is idle when RequestCount is less than 10 per day for 14 consecutive days. Use CloudWatch RequestCount metric with Period of 86400.

{
  "MetricDataQueries": [
    {
      "Id": "request-count",
      "MetricStat": {
        "Metric": {
          "Namespace": "AWS/ApplicationELB",
          "MetricName": "RequestCount",
          "Dimensions": [
            { "Name": "LoadBalancer", "Value": "app/my-alb/1234567890abcdef" }
          ]
        },
        "Period": 86400,
        "Stat": "Sum"
      },
      "ReturnData": true
    }
  ],
  "StartTime": "2023-09-15T00:00:00Z",
  "EndTime": "2023-10-01T00:00:00Z"
}

The Sum over 86400 seconds (1 day) gives total requests per day. A Sum less than 10 for 14 days indicates low usage. A common failure: an ALB is created for a staging environment but never receives traffic. The RequestCount metric remains flat at zero, but HealthyHostCount may be non-zero due to target group health checks, leading to a misclassification.

Classifying Waste by Duration and Impact

After identifying idle resources, classify them by duration and cost impact.

Short-Term Waste

Resources idle for less than 7 days but expected to be active soon. These include:

For such resources, the cost of keeping them alive outweighs the benefit of quick startup.

Medium-Term Waste

Resources idle for 7 to 30 days. These are typically:

They can be transitioned to a lower tier or scheduled to start/stop.

Long-Term Waste

Resources idle for more than 30 days. These are:

They are prime candidates for termination.

Automating Waste Elimination

Eliminate waste by automating the lifecycle of idle resources. This involves three steps: signal, action, and verification.

Signal: Trigger on Idle Condition

Use event-driven systems to detect idle resources. A common pattern is to run a daily job that queries metrics and emits events.

#!/bin/bash


# Query CloudWatch for idle EC2 instances
AWS_TOKEN=$(aws sts assume-role --role-arn arn:aws:iam::123456789012:role/CloudWasteDetector --query 'Credentials.AccessToken' --output text)

# Fetch CPU and network metrics for all instances
for instance_id in $(aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' --output text); do
  response=$(curl \
    --header "Authorization: Bearer $AWS_TOKEN" \
    --header "Content-Type: application/json" \
    --data '{
      "MetricDataQueries": [
        {
          "Id": "cpu",
          "MetricStat": {
            "Metric": {
              "Namespace": "AWS/EC2",
              "MetricName": "CPUUtilization",
              "Dimensions": [
                { "Name": "InstanceId", "Value": "'"$instance_id"'" }
              ]
            },
            "Period": 3600,
            "Stat": "Average"
          },
          "ReturnData": true
        }
      ],
      "StartTime": "2023-10-01T00:00:00Z",
      "EndTime": "2023-10-04T00:00:00Z"
    }' \
    https://monitoring.us-east-1.amazonaws.com)

  # Extract average CPU
  avg_cpu=$(echo "$response" | jq -r '.MetricDataResults[0].Values[0]')

  # Check if average CPU < 10
  if (( $(echo "$avg_cpu < 10" | bc -l) )); then
    echo "Idle instance: $instance_id"
    # Emit event to message queue
    echo "{
      \"resource_type\": \"EC2\",
      \"instance_id\": \"$instance_id\",
      \"reason\": \"CPU < 10% for 72 hours\",
      \"detected_at\": \"$(date -Iseconds)\"
    }" | kafka-console-producer.sh --broker-list kafka.example.com:9092 --topic cloud-waste-identified
  fi
done

This script runs daily as a cron job. It emits structured events to a Kafka topic. Each event includes the resource type, identifier, reason for being idle, and detection timestamp.

Action: Apply Policies to Eliminate Waste

Based on the signal, apply policies to reduce or remove waste.

Stop Idle EC2 Instances

Create a policy that stops instances after 7 days of idle CPU.

{
  "name": "stop-idle-instances",
  "schedule": "0 2 * * *",
  "actions": [
    {
      "type": "stop-instances",
      "filter": {
        "metric": "CPUUtilization",
        "threshold": 10,
        "duration": "7d",
        "period": "1h"
      },
      "parameters": {
        "force": true,
        "deregister": false,
        "stop-time": "2023-10-04T02:00:00Z"
      }
    }
  ]
}

The policy runs every day at 2:00 AM. It checks for instances with CPUUtilization below 10% over a 7-day period, with hourly data. It stops them, forces the stop even if the instance is in use, and records the stop time.

Terminate Long-Term Idle Resources

For resources idle for more than 30 days, schedule termination.

{
  "name": "terminate-long-idle-resources",
  "schedule": "0 3 1 * *",
  "actions": [
    {
      "type": "terminate-resources",
      "filter": {
        "metric": "RequestCount",
        "threshold": 10,
        "duration": "14d",
        "period": "1d"
      },
      "parameters": {
        "dry-run": false,
        "reason": "Long-term idle: terminated via cloud-waste automation"
      }
    }
  ]
}

This policy runs monthly on the first day of the month. It identifies ALBs with less than 10 requests per day for 14 days and terminates them.

Verification: Confirm Waste Elimination

After applying actions, verify that waste has been eliminated.

Check Success via Metrics

After a termination, monitor RequestCount and CPUUtilization to ensure the resource is no longer needed.

{
  "type": "verify-termination",
  "query": "SELECT count(*) FROM cloud_waste_events WHERE action = 'terminate' AND status = 'success'",
  "threshold": 1,
  "timeout": "30m",
  "check": {
    "metric": "RequestCount",
    "expected": 0,
    "duration": "7d",
    "period": "1d"
  }
}

The verification job runs after each termination. It checks that the RequestCount remains at zero for 7 days. If not, it logs a failure.

Log Failure Conditions

Common failure modes in waste elimination:

Fine-Grained Waste Detection Patterns

Waste detection is not one-size-fits-all. Use patterns to handle edge cases.

Batch Detection with Incremental Updates

Instead of reprocessing all data every time, use incremental updates.

This reduces cost and latency.

Weighted Idle Detection

Combine multiple metrics into a weighted score.

{
  "type": "weighted-idle",
  "metrics": [
    {
      "name": "CPUUtilization",
      "weight": 0.5,
      "threshold": 10,
      "duration": "7d",
      "period": "1h"
    },
    {
      "name": "NetworkIn",
      "weight": 0.3,
      "threshold": 1000000,
      "duration": "7d",
      "period": "1h"
    },
    {
      "name": "ReadOps",
      "weight": 0.2,
      "threshold": 0,
      "duration": "14d",
      "period": "1d"
    }
  ],
  "threshold": 1.2,
  "score": "sum"
}

A resource is idle if the total score exceeds 1.2. This allows a resource to be considered idle even if one metric is slightly off.

Summary

Cloud waste detection is the continuous process of identifying and eliminating underutilized resources. It begins with measuring utilization across compute, storage, databases, and networking. Resources are classified by duration and impact. Policies are applied to stop, resize, or terminate them. Verification ensures success. The mechanism is flexible, scalable, and resilient to edge cases. It works across platforms and tools, relying on standard APIs, metrics, and event formats. The key is not just detection, but action—transforming insight into cost reduction.

This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.