Arvind Verma

AWS cost optimisation checklist for small teams

An AWS cost-cutting checklist for small teams: budgets, anomaly alerts, rightsizing, Savings Plans, Graviton, Spot, gp3, idle resources, S3 and log retention.

By Arvind Verma13 min read

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
  1. The order matters
  2. 1. Turn on visibility
  3. 2. Delete what nobody uses
  4. 3. Public IPv4 addresses now cost money
  5. 4. NAT gateway data processing
  6. 5. Rightsize with Compute Optimizer
  7. 6. Move to Graviton
  8. 7. Move every gp2 volume to gp3
  9. 8. Commit: Savings Plans or Reserved Instances
  10. 9. Spot for work that can be interrupted
  11. 10. S3 storage classes and lifecycle rules
  12. 11. CloudWatch Logs retention
  13. 12. Data transfer
  14. Price reference
  15. 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

  1. See the spend so you know where the money goes and hear about spikes within a day.
  2. Delete waste, because a resource nobody uses is 100% savings with no performance trade-off.
  3. Rightsize and modernise (smaller instances, Graviton, gp3) so the baseline shrinks.
  4. 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
  • project and environment tags 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

  1. Amazon VPC pricing (public IPv4, NAT gateway). Checked on 11 November 2026. aws.amazon.com
  2. New: AWS Public IPv4 Address Charge and Public IP Insights (AWS News Blog) aws.amazon.com
  3. Amazon EBS pricing. Checked on 11 November 2026. aws.amazon.com
  4. Amazon EBS General Purpose SSD volumes (gp2 and gp3) docs.aws.amazon.com
  5. Modify an Amazon EBS volume using Elastic Volumes operations docs.aws.amazon.com
  6. Pricing and billing for archiving Amazon EBS snapshots docs.aws.amazon.com
  7. Amazon S3 pricing. Checked on 11 November 2026. aws.amazon.com
  8. Understanding and managing Amazon S3 storage classes docs.aws.amazon.com
  9. Transitioning objects using Amazon S3 Lifecycle docs.aws.amazon.com
  10. Amazon CloudWatch pricing (Logs). Checked on 11 November 2026. aws.amazon.com
  11. CloudWatch Logs: Change log data retention docs.aws.amazon.com
  12. AWS CLI reference: logs put-retention-policy docs.aws.amazon.com
  13. Elastic Load Balancing pricing. Checked on 11 November 2026. aws.amazon.com
  14. Amazon EC2 On-Demand pricing: data transfer. Checked on 11 November 2026. aws.amazon.com
  15. Amazon CloudFront pricing. Checked on 11 November 2026. aws.amazon.com
  16. Gateway endpoints for Amazon S3 docs.aws.amazon.com
  17. Savings Plans pricing (Compute and EC2 Instance). Checked on 11 November 2026. aws.amazon.com
  18. Savings Plans FAQs (including Database Savings Plans) aws.amazon.com
  19. Amazon EC2 Reserved Instances pricing aws.amazon.com
  20. Amazon EC2 Spot Instances aws.amazon.com
  21. Spot Instance interruption notices docs.aws.amazon.com
  22. AWS Graviton processor aws.amazon.com
  23. AWS Compute Optimizer pricing. Checked on 11 November 2026. aws.amazon.com
  24. AWS Compute Optimizer: Resource requirements docs.aws.amazon.com
  25. AWS Compute Optimizer: EC2 instance metrics docs.aws.amazon.com
  26. AWS Compute Optimizer: Viewing idle resource recommendations docs.aws.amazon.com
  27. AWS Budgets pricing. Checked on 11 November 2026. aws.amazon.com
  28. AWS Cost Explorer pricing. Checked on 11 November 2026. aws.amazon.com
  29. Detecting unusual spend with AWS Cost Anomaly Detection docs.aws.amazon.com
  30. AWS Cost Anomaly Detection now automatically configured for all new Cost Explorer users aws.amazon.com
  31. Identifying opportunities with Cost Optimization Hub docs.aws.amazon.com