AWS bills rarely grow because of one big mistake. They grow from dozens of small leftovers: a test server nobody stopped, volumes from deleted instances, snapshots from two years ago, and logs kept forever. Each costs a few dollars. Together they can be a large part of the bill.
These are the checks we run first on any AWS account we take over, roughly in order of how often they find money.
You need the AWS CLI configured with read access to billing and EC2. Most commands below only show the current region, so run them for each region you use.
1. Find where the money actually goes
Start with AWS Cost Explorer. Group the last three months by Service, then by Usage type. You are looking for the five lines that make up most of the bill. That is where the savings are.
Pay attention to usage types you don't recognise. NatGateway-Bytes, DataTransfer-Out-Bytes and TimedStorage-ByteHrs often surprise people.
If several teams or projects share one account, turn on cost allocation tags (for example project and environment) in the Billing console. Future reports can then be split by tag.
2. Delete unattached EBS volumes
When an instance is terminated, its extra volumes often stay behind and keep billing every month.
aws ec2 describe-volumes --filters Name=status,Values=available \
--query "Volumes[].{ID:VolumeId,GB:Size,Type:VolumeType,Created:CreateTime}" --output table
Every volume listed is attached to nothing. Take a snapshot of any you're unsure about, then delete it.
3. Release unused Elastic IPs and review public IPv4
AWS charges for every public IPv4 address, whether it is attached to a running instance or not. Unattached Elastic IPs are pure waste:
aws ec2 describe-addresses --query "Addresses[?AssociationId==null].[PublicIp,AllocationId]" --output table
Release the ones you don't need. Also check whether every instance actually needs a public IP. Servers behind a load balancer usually don't.
4. Clean up old snapshots and AMIs
Snapshots are cheap one at a time but pile up, especially from automated backups with no expiry.
aws ec2 describe-snapshots --owner-ids self \
--query "sort_by(Snapshots,&StartTime)[].[SnapshotId,VolumeSize,StartTime,Description]" --output table
Delete snapshots you no longer need, and deregister old AMIs first, because a snapshot used by an AMI can't be deleted. Then set an expiry so it doesn't happen again: Amazon Data Lifecycle Manager or AWS Backup can create and delete snapshots on a schedule.
5. Move gp2 volumes to gp3
gp3 costs less per GB than gp2 and includes 3,000 IOPS and 125 MB/s at any size. The change is online, with no downtime.
aws ec2 describe-volumes --filters Name=volume-type,Values=gp2 \
--query "Volumes[].[VolumeId,Size]" --output table
aws ec2 modify-volume --volume-id vol-0123456789abcdef0 --volume-type gp3
The same applies to RDS: databases on gp2 storage can be modified to gp3.
6. Rightsize oversized instances
Instances are often sized for a launch-day guess and never revisited. Turn on AWS Compute Optimizer (free for the basic recommendations). After it has collected some usage data, it lists over-provisioned EC2 instances, Auto Scaling groups, EBS volumes and Lambda functions, with a suggested size.
Check the memory usage too, not just CPU. CloudWatch doesn't report memory without the CloudWatch agent, so a server that looks idle on CPU might be using most of its RAM.
Two more things to look at while you're there:
- Old instance generations. Moving from
m4ort2to the current generation usually gives more performance for the same or a lower price. - Graviton (ARM) instances. Instance types ending in
g, such asm7gort4g, are often cheaper per hour than the Intel or AMD equivalent. Most Linux workloads run on them without changes, but test first.
7. Turn off what nobody uses at night
Development, staging and test environments rarely need to run 24 hours a day. A server running only during working hours on weekdays is running for less than a third of the week.
Use AWS Instance Scheduler, a scheduled Lambda function, or an EventBridge Scheduler rule to stop EC2 and RDS instances in the evening and start them in the morning. Tag the resources (for example schedule=office-hours) so the rule finds them automatically.
8. Check NAT gateway and data transfer charges
NAT gateways charge per hour and for every GB they process. A common source of a large bill is servers in private subnets downloading from S3, or pulling container images, through the NAT gateway.
- Add a gateway VPC endpoint for S3 (and DynamoDB if used). Gateway endpoints are free, and S3 traffic then bypasses the NAT gateway.
- Use interface endpoints for other heavy AWS APIs, such as ECR, when the traffic is large enough to justify their hourly cost.
- In test environments, check whether you need a NAT gateway in every Availability Zone.
For traffic to the internet, putting CloudFront in front of your website or downloads usually lowers the cost per GB compared with serving straight from EC2 or S3.
9. Set log retention and S3 lifecycle rules
By default, CloudWatch log groups keep logs forever. Find the ones with no retention set:
aws logs describe-log-groups \
--query "logGroups[?retentionInDays==null].[logGroupName,storedBytes]" --output table
aws logs put-retention-policy --log-group-name /app/production --retention-in-days 30
Choose retention based on what you actually need for troubleshooting and compliance, then archive anything longer-term to S3.
For S3, add lifecycle rules that move old objects to cheaper storage classes, delete old object versions, and abort incomplete multipart uploads. If access patterns are unpredictable, S3 Intelligent-Tiering moves objects between tiers automatically.
10. Commit to steady usage, and set alerts
Once the waste is gone, cover the usage that runs all the time with Savings Plans or Reserved Instances. Cost Explorer's recommendations page shows how much commitment matches your last 7, 30 or 60 days. Commit to slightly less than your steady baseline, not the peak.
Then make sure the next surprise is caught quickly:
- AWS Budgets: set a monthly budget with email alerts at 80% and 100% of the expected spend.
- Cost Anomaly Detection: free, and alerts you when a service suddenly costs more than usual, for example a runaway Lambda function or a leaked access key starting crypto-mining instances.
A note on security and cost
Some of the biggest AWS bill surprises are security incidents: leaked access keys used to launch large instances in regions you never use. Turn on MFA for every user, avoid long-lived access keys, and use an SCP or IAM policy to deny unused regions. A cost anomaly alert often finds a compromise before anything else does.
When to get help
Cleaning up idle resources is a one-afternoon job. Keeping the bill down is ongoing: rightsizing as traffic changes, renewing commitments, and watching for new waste. Our AWS managed services team reviews costs as part of running your account, alongside cloud server management and managed DevOps for the pipelines and infrastructure code that create those resources.

