Cost Saving Tricks for AWS: 20 Ways to Save Cost on AWS
Cost saving tricks for AWS: 20 quick wins with us-east-1 prices and a read-only CLI check each, from gp3 templates to Bedrock batch and Extended Support.
Most cost saving tricks for AWS aren’t clever. They’re defaults nobody changed: a launch template still asking for gp2, a subnet handing every instance a public address, a log group set to keep everything forever. Each one takes minutes to spot once you know where to look, and none of them needs a meeting.
This is a list of twenty of those. Every item has a us-east-1 list price at the time of writing, a read-only command to check your own account, and a link to the post where we go into it properly. If you want the five big levers instead (cleanup, rightsizing, commitments, storage tiers, NAT), start with how to save big on your AWS bill. These AWS cost saving tips are what comes after.
How to use the checks
Every command below only reads, unless it says otherwise. Run them per region. Two of them call Cost Explorer, which bills $0.01 per API request, and both use last month’s dates, computed like this:
# Last full calendar month (GNU date first, BSD/macOS date as the fallback)
START=$(date -u -d "$(date -u +%Y-%m-01) -1 month" +%F 2>/dev/null || date -u -v1d -v-1m +%F)
END=$(date -u +%Y-%m-01)
Cost saving tricks for AWS storage and logs
1. Fix gp2 at the source
gp3 is $0.08 per GB-month against $0.10 for gp2, with 3,000 IOPS included. Converting existing volumes helps, but new gp2 volumes keep appearing if the launch template or AMI still asks for them.
aws ec2 describe-launch-template-versions --versions '$Latest' \
--query 'LaunchTemplateVersions[?contains(LaunchTemplateData.BlockDeviceMappings[].Ebs.VolumeType || `[]`, `"gp2"`)].[LaunchTemplateName,VersionNumber]'
aws ec2 describe-images --owners self \
--query 'Images[?contains(BlockDeviceMappings[].Ebs.VolumeType || `[]`, `"gp2"`)].[ImageId,Name]'
Large gp2 volumes can outperform gp3’s baseline, so check those first. Details in idle and orphaned AWS resources.
2. Abort incomplete multipart uploads
Parts of a failed upload bill at the bucket’s storage rate ($0.023 per GB-month in S3 Standard) and never show up in an object listing.
for b in $(aws s3api list-buckets --query "Buckets[].Name" --output text); do
n=$(aws s3api list-multipart-uploads --bucket "$b" --query 'length(Uploads || `[]`)' --output text 2>/dev/null)
[ "${n:-0}" != "0" ] && echo "$b: $n incomplete uploads"
done
The fix is a lifecycle rule, and put-bucket-lifecycle-configuration replaces every rule on the bucket. Use this only on a bucket with no lifecycle configuration yet:
# CHANGES CONFIGURATION: overwrites the bucket's lifecycle rules
B=my-bucket
aws s3api get-bucket-lifecycle-configuration --bucket "$B" # must say NoSuchLifecycleConfiguration
read -p "Add abort-after-7-days as the only rule on $B? (yes/no) " ok
[ "$ok" = yes ] && aws s3api put-bucket-lifecycle-configuration --bucket "$B" \
--lifecycle-configuration '{"Rules":[{"ID":"abort-mpu","Status":"Enabled","Filter":{"Prefix":""},"AbortIncompleteMultipartUpload":{"DaysAfterInitiation":7}}]}'
If the bucket already has rules, merge this one into them. See S3 storage classes and lifecycle rules.
3. Put a number on “Never expire”
CloudWatch Logs storage is $0.03 per GB-month, and new log groups keep data forever. Log groups from deleted Lambda functions stay behind too.
aws logs describe-log-groups --query 'sum(logGroups[?retentionInDays==null].storedBytes)' --output text
That’s a rough guide to the bytes you’re keeping indefinitely, not the billed figure. Check with each team before you set retention, because some logs are legally required.
4. Send VPC Flow Logs straight to S3
Vended logs cost $0.50 per GB into CloudWatch Logs and $0.25 per GB delivered directly to S3, for the first 10 TB.
aws ec2 describe-flow-logs --query "FlowLogs[].[FlowLogId,ResourceId,LogDestinationType]" --output table
You give up Logs Insights and metric filters, and query with Athena instead. Our CloudWatch Logs to S3 post covers when that’s worth it.
5. Move storage-heavy DynamoDB tables to Standard-IA
Standard-IA storage is $0.10 per GB-month against $0.25, but reads and writes cost about 25% more. It wins once storage is roughly half of a table’s throughput spend.
for t in $(aws dynamodb list-tables --query TableNames --output text); do
aws dynamodb describe-table --table-name "$t" \
--query "Table.[TableName,TableSizeBytes,TableClassSummary.TableClass]" --output text
done
None in the last column means Standard. You get two class changes per table in any 30 days. More in DynamoDB cost optimization.
Network
6. Check every VPC for an S3 gateway endpoint
A NAT gateway charges $0.045 per GB processed. A gateway endpoint for S3 or DynamoDB charges nothing.
R=$(aws configure get region)
for v in $(aws ec2 describe-vpcs --query "Vpcs[].VpcId" --output text); do
n=$(aws ec2 describe-vpc-endpoints --filters Name=vpc-id,Values="$v" \
Name=service-name,Values="com.amazonaws.$R.s3" Name=vpc-endpoint-type,Values=Gateway \
--query "length(VpcEndpoints)" --output text)
echo "$v S3 gateway endpoints: $n"
done
An endpoint only helps if the private subnets’ route tables point at it. NAT gateway and data transfer costs shows how to confirm.
7. Stop subnets handing out public IPv4
Every public IPv4 address costs $0.005 an hour, about $3.65 a month, in use or idle. Releasing idle Elastic IPs is the obvious part. The quieter source is subnets that give every new instance an address.
aws ec2 describe-subnets --filters Name=map-public-ip-on-launch,Values=true \
--query "Subnets[].[SubnetId,VpcId,CidrBlock]" --output table
Turning it off means instances there need a NAT, IPv6 or a load balancer to reach or be reached from the internet, so check what launches into each subnet first.
8. Serve S3 downloads through CloudFront
Internet egress straight from S3 starts at $0.09 per GB. CloudFront’s first 1 TB a month is free, then $0.085 per GB, and fetches from an S3 origin cost nothing.
aws ce get-cost-and-usage --time-period Start="$START",End="$END" \
--granularity MONTHLY --metrics UsageQuantity UnblendedCost \
--filter '{"And":[{"Dimensions":{"Key":"SERVICE","Values":["Amazon Simple Storage Service"]}},{"Dimensions":{"Key":"USAGE_TYPE","Values":["DataTransfer-Out-Bytes"]}}]}'
You’ll pay CloudFront request charges after the free 10 million, and the bucket should sit behind origin access control. See CloudFront cost optimization.
Compute
9. Switch Lambda to arm64
arm64 duration is $0.0000133334 per GB-second against $0.0000166667 on x86_64, 20% less for the same memory.
aws lambda list-functions \
--query "Functions[?Architectures[0]=='x86_64'].[FunctionName,Runtime,MemorySize]" --output table
Pure Python or Node.js functions usually move with a config change. Native dependencies and container images need arm64 builds. The same idea applies to RDS and ElastiCache Graviton classes, covered in rightsizing and Graviton.
10. Tighten Lambda timeouts
A hung call at 1,024 MB with the 900-second maximum timeout bills 900 GB-seconds, about $0.015. Asynchronous events retry twice by default, so each failure bills three times.
aws lambda list-functions --query 'Functions[?Timeout>=`300`].[FunctionName,Timeout,MemorySize]' --output table
Set timeouts to what the function needs plus headroom. Lambda cost optimization covers retries and dead-letter queues.
11. Run CI on Spot
An m7i.xlarge is $0.2016 an hour on demand. AWS puts Spot discounts at up to 90%, and build runners are close to the ideal workload: short, stateless and easy to retry.
aws ec2 describe-spot-price-history --instance-types m7i.xlarge m7g.xlarge \
--product-descriptions "Linux/UNIX" --start-time "$(date -u +%FT%TZ)" \
--query "SpotPriceHistory[].[InstanceType,AvailabilityZone,SpotPrice]" --output table
Spread the runner group across several instance types and make the CI system retry an interrupted job. Release builds that can’t restart halfway are the exception. See EC2 Spot Instances done right.
12. Schedule dev and test off at night
An m7i.large costs $0.1008 an hour: about $73.58 a month running all the time, or about $21.84 if it runs 50 hours a week.
aws ec2 describe-instances --filters Name=instance-state-name,Values=running \
Name=tag:Environment,Values=dev,test,staging \
--query "Reservations[].Instances[].[InstanceId,InstanceType,LaunchTime]" --output table
Use your own tag names. EBS keeps billing while instances are stopped, and a stopped RDS database starts itself again after seven days, so the schedule has to stop it again. AWS’s Instance Scheduler solution handles both.
13. Check Savings Plans utilization before buying more
Buy commitments after rightsizing, and in tranches. Before each new tranche, see whether the last one is fully used.
aws ce get-savings-plans-utilization --time-period Start="$START",End="$END" \
--query "Total.Utilization.UtilizationPercentage" --output text
Anything well under 100% means you’ve already committed past your floor. Reserved Instances vs Savings Plans explains how to size each tranche.
Databases, containers and AI
14. Get off Extended Support
RDS for MySQL, PostgreSQL and Aurora charge $0.10 per vCPU-hour in Extended Support for the first two years, then $0.20. An EKS cluster costs $0.10 an hour on a supported Kubernetes version and $0.60 on extended support, about $365 a month extra per cluster.
aws rds describe-db-instances \
--query "DBInstances[].[DBInstanceIdentifier,Engine,EngineVersion,EngineLifecycleSupport]" --output table
for c in $(aws eks list-clusters --query clusters --output text); do
aws eks describe-cluster --name "$c" --query "cluster.[name,version,upgradePolicy.supportType]" --output text
done
EngineLifecycleSupport shows enrolment, so compare each version with the RDS release calendar. Setting an EKS cluster to STANDARD avoids the fee but lets AWS upgrade it automatically. RDS and Aurora cost optimization has the dates.
15. Check each Aurora cluster’s storage type
Aurora Standard is $0.10 per GB-month plus $0.20 per million I/O requests. I/O-Optimized drops I/O charges but costs $0.225 per GB-month and about 30% more per instance. AWS’s rule of thumb is to switch once I/O passes 25% of the cluster’s spend.
aws rds describe-db-clusters \
--query "DBClusters[?starts_with(Engine,'aurora')].[DBClusterIdentifier,StorageType]" --output table
aurora-iopt1 is I/O-Optimized. You can switch to it once every 30 days.
16. Look at Database Savings Plans
They cover RDS, Aurora, DynamoDB, ElastiCache and more, on one-year no-upfront terms. AWS quotes up to 35% off serverless usage and up to 20% off provisioned instances.
aws savingsplans describe-savings-plans --states active \
--query "savingsPlans[].[savingsPlanType,commitment,end]" --output table
They don’t stack with RDS Reserved Instances on the same usage, and older instance generations may not qualify. See AWS billing discounts you probably missed.
17. Find manual snapshots of deleted databases
Manual RDS snapshots outlive their instance and bill at $0.095 per GB-month once they exceed free backup storage ($0.021 for Aurora).
aws rds describe-db-snapshots --snapshot-type manual \
--query "DBSnapshots[].[DBSnapshotIdentifier,DBInstanceIdentifier,AllocatedStorage,SnapshotCreateTime]" --output table
Compare the second column with your live instances. Ask before deleting; some are kept for audits.
18. Send Bedrock work that can wait to batch or Flex
Nova Pro lists $0.80 per million input tokens and $3.20 per million output tokens. Batch inference and the Flex tier are 50% lower on supported models, input and output.
aws bedrock list-model-invocation-jobs --query "invocationJobSummaries[].[jobName,modelId,status]" --output table
An empty result means nobody has tried batch. Results arrive as a file in S3, and Flex requests queue behind Standard traffic when it’s busy. See Amazon Bedrock cost optimization.
19. Cache the prompt prefix
Claude Sonnet 4 through global inference charges $3.00 per million input tokens, $3.75 to write the cache and $0.30 to read it. Nova cache reads cost a quarter of the input rate.
aws cloudwatch list-metrics --namespace AWS/Bedrock --metric-name CacheReadInputTokenCount
No output means no model has had a cache hit recently. Caches expire after about five minutes without a hit, so features with sparse traffic gain little.
20. Turn on Cost Anomaly Detection
It’s free, and new Cost Explorer users get a services monitor with a daily email for anomalies above $100 and 40% of expected spend.
aws ce get-anomaly-monitors --query "AnomalyMonitors[].[MonitorName,MonitorType]" --output table
aws ce get-anomaly-subscriptions --query "AnomalySubscriptions[].[SubscriptionName,Frequency]" --output table
Its machine learning is good at sudden spikes and blind to waste that’s always been there, which is what most of this list is. Tune the threshold to your bill size. More in AI for AWS cost optimization.
FAQ
What are the quickest cost saving tricks for AWS?
Gateway endpoints, gp3 in templates, log retention and Lambda arm64 are usually the fastest. Each is a configuration change with no new contract and little risk.
Are these AWS cost saving hacks safe to run in production?
The checks are. Every command above only reads, except the lifecycle rule in item 2, which asks for confirmation. The fixes vary: retention, table class and storage type changes all have trade-offs worth a conversation with the owner.
How much can I save cost for AWS with this list?
It depends entirely on your bill. Each item shows the unit price so you can multiply by your own usage. Our honest experience is that two or three items matter a lot in any given account and the rest are small.
Do these replace AWS cost optimization best practices like rightsizing?
No. They sit beside them. Rightsizing, commitments and cleanup move more money; this list catches what those miss. Our top 5 tips cover the big levers.
Pick three this week
Run checks 1, 3 and 6 in your busiest account. Write the monthly cost next to each finding and the name of who owns it, then fix the cheapest one before Friday. If you’d rather not repeat them by hand, Kostrix’s daily scans cover several, including gp2 volumes, log groups with no retention and VPCs sending S3 traffic through a NAT gateway.
