AWS bills rarely jump overnight. They creep up. A test server is left running after a project ends, a database snapshot is kept "just in case", logs are stored forever because nobody set a limit. Each item is small, but together they add up, and because no single person owns the bill, nobody notices.
The fix is not a one-off clean-up. It is a short review on the same day every month, with a clear owner. This checklist is the one we use as a starting point. It assumes you have access to the AWS console with billing permissions, and most checks take a few minutes each.
Before you start: know what you are spending
Open Cost Explorer in the AWS Billing console and group last month's costs by Service, then by Region. Two questions usually point to the biggest wins:
- Which three services make up most of the bill?
- Is anything running in a Region you do not expect to use?
Spend that is in a Region nobody recognises is often a forgotten experiment, and sometimes a sign that credentials have been misused. Either way it is worth a closer look.
The monthly checklist
1. Stopped and idle EC2 instances
A stopped instance does not charge for compute, but its attached storage (EBS volumes) and any Elastic IP addresses still cost money. Look for instances that have been stopped for weeks, and for running instances with very low CPU use over the last 30 days. Either delete them or confirm with the owner that they are still needed.
2. Unattached EBS volumes
When an instance is terminated, its data volumes are not always deleted with it. In the EC2 console, open Volumes and filter by state available. These volumes are attached to nothing and are still billed every month. Take a snapshot first if you are unsure, then delete them.
3. Old snapshots and unused machine images
Snapshots and AMIs (machine images) build up quietly, especially when backups are automated without a retention rule. Review snapshots older than your real recovery needs, and deregister AMIs that are no longer used to launch anything. Then set a lifecycle policy (for example with Amazon Data Lifecycle Manager or AWS Backup) so old copies are removed automatically.
4. Unused public IP addresses
AWS charges for public IPv4 addresses, including Elastic IPs that are allocated but not attached to anything. Release Elastic IPs you no longer need, and check whether every resource that has a public address actually needs one.
5. Idle load balancers and NAT gateways
Load balancers and NAT gateways have an hourly charge whether or not they carry traffic. Look for load balancers with no healthy targets or no requests, and NAT gateways in environments that are no longer used. NAT gateways also charge for the data that passes through them, so a busy one is worth reviewing even if it is in use.
6. Oversized instances and databases
Many servers are sized for a peak that never comes. AWS Compute Optimizer (free to turn on) looks at real usage and recommends smaller or newer instance types for EC2, EBS and Lambda. Treat its suggestions as a starting point: test a change in a non-production environment before resizing anything critical. The same thinking applies to RDS databases.
7. Storage classes and old data in S3
Data that is rarely read does not need the most expensive storage class. Use S3 Storage Lens to see which buckets are growing, then add lifecycle rules that move older objects to a cheaper class or delete them when they are no longer needed. S3 Intelligent-Tiering can do this automatically for data with unpredictable access. While you are there, check for incomplete multipart uploads, which take up space and are easy to clean up with a lifecycle rule.
For EBS, moving older gp2 volumes to gp3 usually lowers the cost for the same or better performance, and can be done without downtime.
8. Logs that are kept forever
By default, CloudWatch Logs keeps log data indefinitely. Open each log group and set a retention period that matches what you genuinely need for troubleshooting and compliance. This is one of the simplest changes on the list and is often overlooked.
9. Resources with no owner
Every resource should have an owner and a purpose. Use tags (for example Owner, Project and Environment) and turn them on as cost allocation tags in the Billing console so costs can be grouped by team or project. Anything without an owner goes on a list, and if nobody claims it within an agreed time, it is a candidate for removal.
10. Budgets, alerts and commitments
Set up AWS Budgets with an alert that emails the right people when spend is on track to exceed the monthly amount. Turn on Cost Anomaly Detection to get notified when a service suddenly costs more than usual.
Once your usage is stable and you have removed waste, look at Savings Plans or Reserved Instances for workloads that run all the time. Commit only to the steady part of your usage, and do it after the clean-up, not before, so you do not lock in spend you were about to remove.
Make it routine, not a project
The checklist only works if it happens every month:
- Pick a fixed day, for example the first working Monday of the month.
- Give one person the job of running it and sharing the results.
- Keep a short list of what was found, who owns it and what was done.
- Turn repeated findings into automation, such as lifecycle rules, log retention, budgets and scheduled shut-down of development environments outside working hours.
Within a few months most of the easy waste is gone, and the review becomes a quick check that nothing new has crept in.
When you would rather not do this by hand
We built an AI agent that runs these checks automatically. It scans your AWS account for idle and unused resources, emails the business owners of each one to confirm whether it can be deleted, and sends a monthly report of what was found and cleaned up, so the waste actually gets handled instead of sitting in a spreadsheet.
If you would like a second pair of eyes on your AWS account, or want to talk about setting up the review for your team, book a free consultation.