Skip to content
MetaCode Innovations

How to Cut Your AWS Bill: 10 Checks That Find Wasted Spend

Ten practical checks that find wasted AWS spend: idle resources, oversized instances, gp2 volumes, NAT gateway traffic, log retention, Savings Plans and more, with the AWS CLI commands to find each one.

By Muhammad Ammar, Founder & Lead EngineerUpdated 5 min read
AWS cloud infrastructure diagram on screen

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 m4 or t2 to the current generation usually gives more performance for the same or a lower price.
  • Graviton (ARM) instances. Instance types ending in g, such as m7g or t4g, 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.

Frequently Asked Questions

How much can I save on AWS?
It depends on how the account was built. Accounts that grew without anyone owning cost often have idle resources and oversized instances, and savings plans alone can cut compute rates substantially compared with on-demand prices. Run the checks in this guide and add up what you find before committing to any number.
Should I buy Savings Plans or Reserved Instances?
Compute Savings Plans are the more flexible choice for most teams: they apply to EC2 in any region, instance family or operating system, and also to Fargate and Lambda. Reserved Instances still make sense for RDS, ElastiCache, OpenSearch and Redshift, which Savings Plans don't cover. Only commit to usage you are confident will still be running for the whole term.
Is it safe to switch EBS volumes from gp2 to gp3?
Yes. The change happens while the volume stays attached and in use, with no downtime. gp3 costs less per GB and includes a baseline of 3,000 IOPS and 125 MB/s whatever the size. Check that any large gp2 volume isn't relying on more throughput than gp3's baseline before switching, or add extra throughput to the gp3 volume.
Do these AWS CLI commands work across all regions?
No. Most EC2, EBS and CloudWatch commands only show resources in the current region. Run them in every region you use, either by adding --region to each command or with a loop over the output of aws ec2 describe-regions.
Will moving to Graviton instances break my application?
Graviton instances use ARM processors. Most code in interpreted languages (Python, Node.js, PHP, Ruby, Java) runs unchanged, and most popular container images are published for ARM. Compiled binaries, some older libraries and some commercial software need an ARM build. Test on a staging server first.

Muhammad Ammar

Founder & Lead Engineer, MetaCode Innovations

Muhammad Ammar is the founder of MetaCode Innovations. He is a DevOps and Linux engineer with 4+ years of hands-on work running production servers, control panels, AWS and container platforms, and holds the EC-Council Certified SOC Analyst credential.

Let an Engineer Handle It.

Describe what is happening and send the address. We will reply with next steps and a quote.