Skip to content

What I check in an AWS cost audit

In an AWS cost audit I start with six places: EC2 instance size, Savings Plans, EBS volumes, public IPv4 addresses, NAT Gateway and CloudWatch logs. Five of them are resources left over from a test or still running on their defaults. The sixth is a discount for steady load that nobody has taken.

You can see each of them yourself. Open Cost Explorer, set the last three months and group costs by Usage type. The usage types are in the table, and Savings Plans have a separate report, described below. In Frankfurt, usage types carry the prefix EUC1-.

What you check Usage type in Cost Explorer
EC2 instances BoxUsage:<instance type>
Volumes and snapshots EBS:VolumeUsage.gp2, EBS:SnapshotUsage
IPv4 addresses PublicIPv4:IdleAddress, PublicIPv4:InUseAddress
NAT Gateway NatGateway-Hours, NatGateway-Bytes
CloudWatch logs DataProcessing-Bytes, TimedStorage-ByteHrs

You can also load a Cost Explorer export into the CSV analysis, which flags most of these. The file stays in your browser. The prices below are on-demand rates in Frankfurt according to AWS pricing on 8 October 2026.

EC2 instances bigger than the traffic needs

An instance is usually sized at launch, with headroom, and nobody looks at it again. Turn on Compute Optimizer in the AWS console. It is free and uses the last 14 days of metrics to show which instances are too big. It only takes memory into account when the CloudWatch agent runs on the instance.

Also check whether the application would run on Graviton, AWS’s own ARM processors. In Frankfurt an m7g.large with Graviton is 19% cheaper than an m7i.large with an Intel processor. Resizing means stopping the instance, but you cannot move to ARM by changing the type alone. You need a new instance from an arm64 image, and for containers an arm64 build in CI as well.

A test environment often runs around the clock while the team uses it ten hours a day, Monday to Friday: 50 of the 168 hours in a week. AWS’s Instance Scheduler starts and stops EC2 and RDS on a schedule you assign to the instance with a tag.

Always-on servers billed on demand

If some instances have run non-stop for months and you pay the on-demand rate for them, you are paying more than you need to. Compute Savings Plans cut the rate by up to 66% in exchange for a one- or three-year commitment.

In Cost Explorer, open Savings Plans and then the Coverage report. It shows how much of your cost a plan covers. Buy a plan only for the capacity that always runs, and only after you have downsized the instances. Buy it earlier and you commit to paying for the headroom for the whole term.

gp2 volumes, orphaned volumes and old snapshots

In Frankfurt a gp3 volume is 20% cheaper than gp2, and you can change the type without detaching the volume. Large gp2 volumes are faster than gp3 at its base price, though: from 334 GiB they have twice the throughput, and above 1,000 GiB more IOPS. For those volumes, buy the missing performance on gp3, or the disk will slow down.

A volume in the available state is not attached to any instance, and you pay for it all the same. The command works in one region, so run it with --region for each one you use:

aws ec2 describe-volumes \
  --filters Name=status,Values=available \
  --query "Volumes[].[VolumeId,Size,VolumeType,CreateTime]" \
  --output table

Before deleting a volume, take a snapshot of it. It costs $0.054 per GB-month, less than the volume, and lets you restore the data.

Snapshots pile up when a backup script creates copies and never deletes any. Replace the script with a Data Lifecycle Manager policy that deletes old copies itself. Delete the script’s older copies by hand, because DLM does not touch them.

Public IPv4 addresses

Since February 2024 AWS has charged $3.65 a month for every public IPv4 address, including one attached to a running instance.

IdleAddress on the bill means addresses nothing is using. Release each one that is not in DNS or on a customer’s or supplier’s allowlist. The free Public IP Insights shows what each address is attached to. An instance behind a load balancer rarely needs its own public IP. If it connects to the internet itself, though, without an address it will need a NAT gateway, which costs more than ten addresses.

Traffic through NAT Gateway

A NAT gateway is the gateway through which servers without a public address reach the internet. In Frankfurt it costs about $38 a month, plus $0.052 for every GB that passes through it. When such a server reads and writes files in S3, all of that traffic goes through the gateway by default.

If NatGateway-Bytes costs more than NatGateway-Hours, find out where the application sends its traffic. Traffic to S3 and DynamoDB in the same region can go through a gateway endpoint, which AWS does not charge for. It is a route table entry, so the application code stays as it is.

CloudWatch logs

In Frankfurt ingesting a gigabyte of logs costs $0.63, and CloudWatch Logs keeps log data indefinitely by default.

In the CloudWatch console, open Log groups and sort by size. For groups showing Never Expire in the Retention column, set the period you actually need, for example 30 days for application logs. If DataProcessing-Bytes costs more than TimedStorage-ByteHrs, reduce the volume of logging in the application itself.

What to do this week

Create a budget in AWS Budgets with an e-mail alert. Budgets and their notifications are free, and the alert arrives when actual or forecast spend for the month crosses the amount you set. After that, activate your environment tags as cost allocation tags so Cost Explorer can separate staging from production.

Leave changes to instances, volumes and NAT with a developer, because each one needs testing. The sample report shows the result: a list of such changes, each with its monthly amount. It uses sample data that does not come from any client.

3 ways I can help

  • What EC2-Other means on your AWS bill

    Read this post next.

  • AWS cost audit

    An AWS cost audit at a fixed price per package. You get a list of changes with a saving figure next to each, and I only need read-only access to the account.

  • Email me

    Or directly to jacob@codelevel.pl. I reply within 1 business day.