In short
- Get visibility first: budgets, Cost Anomaly Detection and cost allocation tags cost little or nothing and catch surprises within a day.
- Delete waste before you commit: unattached volumes, idle NAT gateways and load balancers, and public IPv4 at $0.005 an hour each.
- Rightsize and move to Graviton and gp3 first, then buy Savings Plans for the smaller baseline that is left.
On this page
- The order matters
- 1. Turn on visibility
- 2. Delete what nobody uses
- 3. Public IPv4 addresses now cost money
- 4. NAT gateway data processing
- 5. Rightsize with Compute Optimizer
- 6. Move to Graviton
- 7. Move every gp2 volume to gp3
- 8. Commit: Savings Plans or Reserved Instances
- 9. Spot for work that can be interrupted
- 10. S3 storage classes and lifecycle rules
- 11. CloudWatch Logs retention
- 12. Data transfer
- Price reference
- Monthly review checklist
On a small AWS team without a FinOps department, the builders also own the bill: a startup with one or two engineers, an agency hosting client sites, or a founder who opened Cost Explorer and did not like what they saw. The checklist goes in the order that saves the most money for the least risk, with commands you can run today.
Cost work is a regular part of my job. I re-architected one client’s AWS footprint for about 30% lower operating costs, have cut other clients’ infrastructure bills by up to 40%, and moved more than 60 websites to AWS on my own. This is the order I work in.
All prices below are On-Demand list prices for US East (N. Virginia), checked on 11 November 2026. Other Regions cost more or less; check your own Region before you plan around a number. A month is taken as 730 hours.
The order matters
- See the spend so you know where the money goes and hear about spikes within a day.
- Delete waste, because a resource nobody uses is 100% savings with no performance trade-off.
- Rightsize and modernise (smaller instances, Graviton, gp3) so the baseline shrinks.
- Commit with Savings Plans on the smaller baseline that is left.
Teams that buy a three-year commitment first lock in their waste for three years.
1. Turn on visibility
Cost Explorer. Enable it in the Billing and Cost Management console. Group by Service, then by Usage type: usage types containing NatGateway-Bytes or PublicIPv4:InUseAddress tell you exactly what you are paying for. The console is the cheap way to use it; the Cost Explorer API costs $0.01 per request.
Budgets. Watching budgets and receiving alerts is free. Only action-enabled budgets cost money, and the first two of those are free each month, then $0.10 per budget per day. Create at least:
- A monthly cost budget at your expected spend, alerting at 80% actual and 100% forecast.
- A smaller budget for any account where engineers experiment.
Cost Anomaly Detection. It uses machine learning to flag spend that breaks your normal pattern and comes at no additional cost. New Cost Explorer users since March 2023 get an AWS services monitor and a daily email summary automatically, so check for one before creating another, and point the alerts at someone who can act. Because it reads Cost Explorer data, detection can lag usage by up to 24 hours, and a brand-new service needs 10 days of history first.
Cost allocation tags. Tag resources with at least project and environment, then activate those tag keys in the Billing console so Cost Explorer can group by them. Untagged spend is the spend nobody owns.
2. Delete what nobody uses
Waste hides in resources that keep billing after the thing they served is gone.
Unattached EBS volumes
A volume in the available state is attached to nothing and still billed every month.
aws ec2 describe-volumes --filters Name=status,Values=available \
--query 'Volumes[].{ID:VolumeId,GiB:Size,Type:VolumeType,Created:CreateTime}' \
--output table
Snapshot anything you are unsure about, then delete it. A forgotten 500 GiB gp3 volume costs $40 a month.
Old snapshots
Snapshots of deleted volumes and old AMIs add up. Standard snapshot storage is $0.05 per GB-month.
aws ec2 describe-snapshots --owner-ids self --output json \
--query 'Snapshots[?StartTime<=`2025-11-11`].{ID:SnapshotId,GiB:VolumeSize,Started:StartTime,Desc:Description}'
For snapshots you must keep for compliance but rarely restore, the EBS Snapshots Archive tier costs $0.0125 per GB-month, a quarter of standard. Two catches: an archived snapshot is stored as a full snapshot rather than incremental, and it has a 90-day minimum archive period, so deleting it earlier is billed for the remaining days. Restores cost $0.03 per GB.
Idle load balancers
An Application Load Balancer costs $0.0225 per hour, about $16.43 a month, before capacity units ($0.008 per LCU-hour); Network Load Balancers have the same hourly rate. Check the RequestCount (ALB) or ActiveFlowCount (NLB) metric in CloudWatch and delete the ones that sit at zero. Putting many small sites behind one ALB with host-based rules is often the bigger win.
Idle NAT gateways, databases and other leftovers
AWS Compute Optimizer produces idle recommendations for EC2 instances, Auto Scaling groups, EBS volumes, RDS and Aurora databases, NAT gateways, ElastiCache, DynamoDB tables and more. Its definitions are sensible: for example, an EC2 instance is idle when peak CPU stays below 5% and network I/O below 5 MB a day over 14 days, and an EBS volume is unattached when it has not been attached to anything for 32 days. If a database is only idle temporarily, RDS for MySQL and PostgreSQL instances can be stopped for up to seven days at a time.
Development environments that run all night
Non-production instances that only need to run during working hours (say 50 hours a week, about 217 hours a month) cost roughly 70% less in compute than the same instances left on for 730 hours. Schedule them to stop overnight and at weekends. Their EBS volumes still bill while stopped.
3. Public IPv4 addresses now cost money
Since 1 February 2024, AWS charges $0.005 per hour for every public IPv4 address, in use or idle. That covers Elastic IPs as well as the public IPs auto-assigned to instances. Each address is about $3.65 a month; ten are $438 a year.
Find Elastic IPs that are not associated with anything:
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==`null`].[PublicIp,AllocationId]' --output table
Then reduce the rest:
- Release unassociated Elastic IPs you no longer need.
- Stop auto-assigning public IPs in subnets where instances sit behind a load balancer. They don’t need one.
- Use Public IP Insights in VPC IP Address Manager to list every public IPv4 address in the account. AWS made it available at no cost when the charge was announced.
- Use IPv6 where your clients support it. It removes the per-address charge.
4. NAT gateway data processing
NAT gateways are easy to underestimate. Each costs $0.045 per hour (about $32.85 a month) plus $0.045 per GB processed, and that is on top of normal data transfer charges. A common design puts one in each of three Availability Zones, which is about $98.55 a month before a single byte moves.
The biggest fixes:
- Gateway endpoints for S3 and DynamoDB. AWS charges nothing for gateway VPC endpoints. Without one, traffic from private subnets to S3 runs through the NAT gateway and pays the $0.045 per GB processing fee. Backups, logs and container image layers stored in S3 add up fast. Creating the endpoint changes routes and drops open TCP connections to S3, so do it in a quiet window:
aws ec2 create-vpc-endpoint --vpc-id vpc-0abc1234 \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids rtb-0abc1234 rtb-0def5678
- Find out who is talking. VPC Flow Logs on the NAT gateway’s network interface show which instances send the most traffic and where to.
- One NAT gateway for non-production. Production may need one per AZ for resilience; a staging VPC usually does not.
5. Rightsize with Compute Optimizer
Instance sizes are usually picked on launch day, before anyone knows the real load. Compute Optimizer compares them with what they actually use. It has no additional charge for the default 14-day and 32-day lookbacks; the optional enhanced infrastructure metrics cost $0.0003360215 per resource per hour, about $0.25 a month for a resource running all month, and extend the lookback to three months.
aws compute-optimizer update-enrollment-status --status Active
What to know before trusting the results:
- An EC2 instance needs at least 30 hours of CloudWatch metrics in the past 14 days before it gets a recommendation. Analysis can take up to 24 hours after you opt in.
- Memory is invisible by default. Compute Optimizer only analyses memory utilisation if the CloudWatch agent is installed on the instance, or if it ingests memory metrics from Datadog, Dynatrace, Instana or New Relic. Without memory data, a “downsize” recommendation can starve a memory-heavy app. If you already monitor hosts with New Relic, set up that ingestion rather than adding a second agent (my notes on New Relic’s free tier for infrastructure monitoring cover the agent).
- Opt in to Cost Optimization Hub as well. It pulls rightsizing, idle-resource, Graviton and Savings Plans recommendations into one list and estimates savings using your actual discounts.
Downsize one size at a time, watch for a week, then repeat.
6. Move to Graviton
AWS states that Graviton-based instances cost up to 20% less than comparable x86 instances and use up to 60% less energy for the same performance. For most Linux workloads in interpreted or managed languages (Node.js, Python, PHP, Java, Go, Ruby) the move is a rebuild of the container image or AMI for arm64 and a test run. Managed services such as RDS, Aurora and ElastiCache offer Graviton instance classes too, and those migrations are usually a modify-and-reboot.
Watch for native dependencies, base images with no arm64 variant, and agents that only ship x86 builds. AWS offers a free trial of t4g.small for up to 750 hours a month until 31 December 2026, a cheap way to test.
7. Move every gp2 volume to gp3
gp3 is 20% cheaper per GiB than gp2 ($0.08 against $0.10 per GB-month) and includes 3,000 IOPS and 125 MiB/s baseline regardless of size. You can change the type in place with Elastic Volumes, without detaching the volume or stopping the instance.
# List what is left on gp2
aws ec2 describe-volumes --filters Name=volume-type,Values=gp2 \
--query 'Volumes[].[VolumeId,Size,State]' --output table
# Convert one
aws ec2 modify-volume --volume-id vol-0123456789abcdef0 --volume-type gp3
One detail changes the maths on large volumes. gp2 performance grows with size: 3 IOPS per GiB, and up to 250 MiB/s throughput from 334 GiB. If you convert without specifying performance, AWS provisions gp3 to match whichever is higher, the old gp2 figures or the gp3 baseline. Extra IOPS cost $0.005 per IOPS-month and extra throughput about $0.04 per MiB/s-month.
Worked example, 500 GiB volume:
| Option | Storage | Extra performance | Monthly |
|---|---|---|---|
| gp2 (1,500 IOPS, 250 MiB/s) | $50.00 | included | $50.00 |
| gp3 converted as-is (3,000 IOPS, 250 MiB/s) | $40.00 | +125 MiB/s = $5.00 | $45.00 |
| gp3 at baseline (3,000 IOPS, 125 MiB/s) | $40.00 | none | $40.00 |
Check the volume’s real throughput in CloudWatch first. If it never comes near 125 MiB/s, set --throughput 125 and take the full 20%. A volume can be modified up to four times in a rolling 24 hours, so you can adjust if you get it wrong.
8. Commit: Savings Plans or Reserved Instances
Only commit once the steps above have shrunk your baseline. Then commit to the part of your spend that runs 24/7 no matter what.
| Option | Maximum discount | Covers | Flexibility |
|---|---|---|---|
| Compute Savings Plans | up to 66% | EC2, Fargate, Lambda | Any instance family, size, Region, OS |
| EC2 Instance Savings Plans | up to 72% | EC2 in one family and Region | Size, OS and tenancy within that family |
| Database Savings Plans | up to 35% | RDS, Aurora, DynamoDB, ElastiCache, DocumentDB, Neptune, OpenSearch and others | 1-year, no upfront only |
| Standard Reserved Instances | up to 72% | One instance type | Can be sold on the RI Marketplace |
| Convertible Reserved Instances | up to 66% | One instance type | Can be exchanged for equal or greater value |
For a small team, a one-year, no-upfront Compute Savings Plan is usually the right first purchase. It survives a move to Graviton, a new instance family, a shift from EC2 to Fargate, or a Region change. Commit to about the lowest hourly spend you saw over the last month, not the average; usage above the commitment is billed at On-Demand rates.
For databases, AWS now recommends moving from Reserved Instances to Database Savings Plans as your RIs expire. The two cannot be combined on the same workload.
9. Spot for work that can be interrupted
Spot Instances use spare EC2 capacity at up to 90% off On-Demand prices. The trade-off: AWS can reclaim them with a two-minute interruption notice, delivered as an EventBridge event and through instance metadata.
Good fits: CI runners, batch jobs, data processing, stateless web workers behind a load balancer, and dev and test environments. Poor fits: single-instance databases and anything holding state on local disk. Spread an Auto Scaling group across several instance types and Availability Zones, and keep a small On-Demand base for capacity you cannot lose.
10. S3 storage classes and lifecycle rules
S3 Standard costs $0.023 per GB-month for the first 50 TB. For data you rarely read, the other classes are much cheaper:
| Storage class | Price per GB-month | Minimum duration | Access |
|---|---|---|---|
| S3 Standard | $0.023 | none | milliseconds |
| S3 Intelligent-Tiering | $0.023 down to $0.004 (plus optional archive tiers) | none | milliseconds for the default tiers |
| S3 Standard-IA | $0.0125 | 30 days | milliseconds, retrieval fee |
| S3 One Zone-IA | $0.01 | 30 days | milliseconds, one AZ only |
| S3 Glacier Instant Retrieval | $0.004 | 90 days | milliseconds, retrieval fee |
| S3 Glacier Flexible Retrieval | $0.0036 | 90 days | minutes to hours |
| S3 Glacier Deep Archive | $0.00099 | 180 days | hours |
Rules that save money and the traps that cost it:
- Unknown access patterns: use Intelligent-Tiering. It moves objects not accessed for 30 days to an infrequent tier and after 90 days to an archive instant tier, with no retrieval fees. It charges $0.0025 per 1,000 objects a month for monitoring, and objects under 128 KB are not monitored and always stay in the Frequent Access tier.
- Small objects are expensive to move. Standard-IA, One Zone-IA and Glacier Instant Retrieval bill at least 128 KB per object. Since September 2024, lifecycle rules by default don’t transition objects under 128 KB at all, because the per-object transition fee can exceed the saving.
- Minimum durations bite. Delete a Standard-IA object after ten days and you still pay for 30.
- Clean up hidden storage. Old object versions in versioned buckets and abandoned multipart uploads are billed but don’t show up in a normal listing.
A lifecycle configuration that covers the common case:
{
"Rules": [
{
"ID": "logs-tiering",
"Filter": { "Prefix": "logs/" },
"Status": "Enabled",
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER_IR" }
],
"Expiration": { "Days": 365 }
},
{
"ID": "bucket-hygiene",
"Filter": {},
"Status": "Enabled",
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
}
]
}
aws s3api put-bucket-lifecycle-configuration --bucket my-bucket \
--lifecycle-configuration file://lifecycle.json
This replaces any existing lifecycle configuration on the bucket, so merge in existing rules first.
11. CloudWatch Logs retention
By default, CloudWatch Logs keeps log data indefinitely. Every Lambda function, container and agent you have ever run may still be storing logs at $0.03 per GB-month, and the pile only grows. Ingestion is the bigger line: $0.50 per GB for the Standard log class, or $0.25 per GB for the Infrequent Access class, which suits logs you keep for audits but rarely query.
Find log groups set to never expire, then set a retention period:
aws logs describe-log-groups \
--query 'logGroups[?retentionInDays==`null`].[logGroupName,storedBytes]' --output table
aws logs put-retention-policy --log-group-name /aws/lambda/my-function \
--retention-in-days 30
Valid values run from 1 day to 3,653 days (1, 3, 5, 7, 14, 30, 60, 90 and so on). To set a baseline for the whole account, use an account-level retention policy (aws logs put-account-policy with --policy-type RETENTION_POLICY); add --dry-run first, because an account policy overrides every matching log group’s own setting, including longer ones you meant to keep.
Then cut ingestion at the source: no debug logging in production, and no shipping the same log to two places.
12. Data transfer
The parts of data transfer that matter for small teams:
- The first 100 GB a month out to the internet is free, aggregated across services and Regions (except China and GovCloud). After that, internet egress from US East starts at $0.09 per GB for the first 10 TB.
- Inbound data is free. Traffic within one Availability Zone over private IPs is free; traffic between Availability Zones is billed ($0.01 per GB in US East).
- Put CloudFront in front of S3 and web origins. Transfer from AWS origins to CloudFront is waived, and CloudFront now offers flat-rate plans, including a $0 plan with 100 GB of transfer and 1 million requests a month.
- Keep chatty services in the same AZ where resilience allows, and avoid cross-Region traffic you don’t need.
- Remember NAT. Traffic that leaves through a NAT gateway pays the processing fee from section 4 on top of egress.
Price reference
US East (N. Virginia), On-Demand, checked on 11 November 2026:
| Item | Price |
|---|---|
| Public IPv4 address (in use or idle) | $0.005 per hour |
| NAT gateway | $0.045 per hour + $0.045 per GB processed |
| Application or Network Load Balancer | $0.0225 per hour + capacity units |
| EBS gp3 / gp2 | $0.08 / $0.10 per GB-month |
| EBS snapshots, standard / archive | $0.05 / $0.0125 per GB-month |
| S3 Standard (first 50 TB) | $0.023 per GB-month |
| CloudWatch Logs ingestion, Standard / Infrequent Access | $0.50 / $0.25 per GB |
| CloudWatch Logs storage | $0.03 per GB-month |
| Internet egress after free 100 GB | from $0.09 per GB |
Monthly review checklist
Work through it in this order and review monthly:
- Cost Explorer on; spend grouped by service and usage type
- Monthly budget with actual and forecast alerts
- Cost Anomaly Detection monitor and alert to someone who can act
projectandenvironmenttags applied and activated- Unattached EBS volumes and orphaned snapshots deleted or archived
- Idle load balancers, NAT gateways, databases and instances removed
- Dev and staging scheduled off outside working hours
- Unassociated Elastic IPs released; public IPs only where needed
- S3 and DynamoDB gateway endpoints in every VPC with a NAT gateway
- Compute Optimizer and Cost Optimization Hub on, with memory metrics
- Instances rightsized; Graviton tested and adopted where it works
- All gp2 volumes on gp3, throughput set to what is actually used
- Compute Savings Plan sized to the lowest steady hourly spend
- Spot used for CI, batch and stateless workers
- S3 lifecycle rules for logs, old versions and incomplete uploads
- Every CloudWatch log group has a retention period
- CloudFront in front of public S3 and web traffic
If you would like a second pair of eyes on your bill, AWS cost reviews and re-architecture are part of the work I do.
Sources
- Amazon VPC pricing (public IPv4, NAT gateway). Checked on 11 November 2026. aws.amazon.com
- New: AWS Public IPv4 Address Charge and Public IP Insights (AWS News Blog) aws.amazon.com
- Amazon EBS pricing. Checked on 11 November 2026. aws.amazon.com
- Amazon EBS General Purpose SSD volumes (gp2 and gp3) docs.aws.amazon.com
- Modify an Amazon EBS volume using Elastic Volumes operations docs.aws.amazon.com
- Pricing and billing for archiving Amazon EBS snapshots docs.aws.amazon.com
- Amazon S3 pricing. Checked on 11 November 2026. aws.amazon.com
- Understanding and managing Amazon S3 storage classes docs.aws.amazon.com
- Transitioning objects using Amazon S3 Lifecycle docs.aws.amazon.com
- Amazon CloudWatch pricing (Logs). Checked on 11 November 2026. aws.amazon.com
- CloudWatch Logs: Change log data retention docs.aws.amazon.com
- AWS CLI reference: logs put-retention-policy docs.aws.amazon.com
- Elastic Load Balancing pricing. Checked on 11 November 2026. aws.amazon.com
- Amazon EC2 On-Demand pricing: data transfer. Checked on 11 November 2026. aws.amazon.com
- Amazon CloudFront pricing. Checked on 11 November 2026. aws.amazon.com
- Gateway endpoints for Amazon S3 docs.aws.amazon.com
- Savings Plans pricing (Compute and EC2 Instance). Checked on 11 November 2026. aws.amazon.com
- Savings Plans FAQs (including Database Savings Plans) aws.amazon.com
- Amazon EC2 Reserved Instances pricing aws.amazon.com
- Amazon EC2 Spot Instances aws.amazon.com
- Spot Instance interruption notices docs.aws.amazon.com
- AWS Graviton processor aws.amazon.com
- AWS Compute Optimizer pricing. Checked on 11 November 2026. aws.amazon.com
- AWS Compute Optimizer: Resource requirements docs.aws.amazon.com
- AWS Compute Optimizer: EC2 instance metrics docs.aws.amazon.com
- AWS Compute Optimizer: Viewing idle resource recommendations docs.aws.amazon.com
- AWS Budgets pricing. Checked on 11 November 2026. aws.amazon.com
- AWS Cost Explorer pricing. Checked on 11 November 2026. aws.amazon.com
- Detecting unusual spend with AWS Cost Anomaly Detection docs.aws.amazon.com
- AWS Cost Anomaly Detection now automatically configured for all new Cost Explorer users aws.amazon.com
- Identifying opportunities with Cost Optimization Hub docs.aws.amazon.com