AWS Cost Optimization: 10 Ways to Reduce Your Bill
Cut your AWS bill with 10 proven cost optimization strategies covering Reserved Instances, right-sizing, S3 tiers, spot instances, and more.
What you'll learn
- ✓Identify and eliminate wasted AWS spend
- ✓Choose between Reserved Instances, Savings Plans, and Spot
- ✓Right-size compute resources using CloudWatch data
- ✓Optimize storage costs with lifecycle policies and intelligent tiering
Prerequisites
- •Active AWS account with running workloads
- •Access to AWS Billing and Cost Explorer
AWS pricing is pay-as-you-go, but without active management, costs spiral quickly. Unused resources, oversized instances, and missed discount opportunities add up to thousands of dollars in waste every month. The good news is that most AWS accounts have 20-40% savings potential waiting to be captured.
Here are ten proven strategies to reduce your AWS bill, ordered from quick wins to longer-term optimizations.
1. Find and Terminate Unused Resources
The easiest savings come from resources you are paying for but not using. These accumulate over time as projects end, environments are abandoned, and experiments are forgotten.
Check for these common culprits:
# Unattached EBS volumes (you pay for these even when not mounted)
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[].{ID:VolumeId,Size:Size,Type:VolumeType,Created:CreateTime}' \
--output table
# Unused Elastic IPs (charged when not associated with a running instance)
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==null].{IP:PublicIp,AllocationId:AllocationId}' \
--output table
# Idle load balancers (no healthy targets)
aws elbv2 describe-target-health \
--target-group-arn <target-group-arn> \
--query 'TargetHealthDescriptions[?TargetHealth.State!=`healthy`]'
# Old snapshots (often forgotten after AMI cleanup)
aws ec2 describe-snapshots --owner-ids self \
--query 'Snapshots[?StartTime<`2026-01-01`].{ID:SnapshotId,Size:VolumeSize,Date:StartTime}' \
--output table
Set up a monthly review to catch newly idle resources. AWS Trusted Advisor (available with Business or Enterprise Support) automates some of these checks.
2. Right-Size Your Instances
Most EC2 instances are larger than they need to be. Developers often choose a size “just in case” and never revisit the decision. AWS Cost Explorer has a right-sizing recommendation feature that analyzes CloudWatch metrics:
# Get right-sizing recommendations
aws ce get-rightsizing-recommendation \
--service EC2 \
--configuration '{
"RecommendationTarget": "SAME_INSTANCE_FAMILY",
"BenefitsConsidered": true
}'
Before downsizing, verify with CloudWatch metrics:
# Check CPU utilization over the past 2 weeks
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
--start-time 2026-06-22T00:00:00Z \
--end-time 2026-07-06T00:00:00Z \
--period 3600 \
--statistics Average Maximum \
--query 'Datapoints | sort_by(@, &Timestamp) | [-48:]'
If the peak CPU stays under 40%, the instance is likely oversized. For memory-bound workloads, install the CloudWatch agent to collect memory metrics since these are not available by default.
Consider the newer instance families too. Graviton-based instances (like m7g, c7g, r7g) typically provide 20-30% better price-performance than their x86 equivalents.
3. Use Savings Plans or Reserved Instances
On-demand pricing is the most expensive way to run steady-state workloads. For predictable usage, Savings Plans and Reserved Instances offer 30-72% discounts.
Savings Plans are the simpler option. You commit to a consistent amount of compute spend (measured in dollars per hour) for 1 or 3 years:
- Compute Savings Plans: Apply across EC2, Fargate, and Lambda regardless of region, instance family, or OS. Most flexible.
- EC2 Instance Savings Plans: Locked to a specific instance family in a specific region. Deeper discounts.
# View Savings Plans recommendations
aws ce get-savings-plans-purchase-recommendation \
--savings-plans-type COMPUTE_SP \
--term-in-years ONE_YEAR \
--payment-option NO_UPFRONT \
--lookback-period-in-days SIXTY_DAYS
Reserved Instances are the older model, offering similar discounts but with less flexibility. They are still useful for RDS, ElastiCache, and Redshift where Savings Plans do not apply.
Start with a No Upfront, 1-year Compute Savings Plan covering your baseline usage. This is the lowest-risk commitment and still saves roughly 30%.
4. Leverage Spot Instances for Fault-Tolerant Workloads
Spot Instances use spare EC2 capacity at up to 90% discount. The tradeoff is that AWS can reclaim them with a 2-minute warning. They work well for:
- Batch processing and data pipelines
- CI/CD build agents
- Stateless web servers behind an Auto Scaling Group
- Machine learning training jobs
# Auto Scaling Group with mixed instances (spot + on-demand)
Resources:
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
MixedInstancesPolicy:
InstancesDistribution:
OnDemandBaseCapacity: 2
OnDemandPercentageAboveBaseCapacity: 25
SpotAllocationStrategy: capacity-optimized
LaunchTemplate:
LaunchTemplateSpecification:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
Overrides:
- InstanceType: m5.large
- InstanceType: m5a.large
- InstanceType: m5n.large
- InstanceType: m4.large
MinSize: 4
MaxSize: 20
This configuration keeps 2 on-demand instances as a baseline and fills 75% of additional capacity with spot. Specifying multiple instance types improves availability since AWS can draw from multiple capacity pools.
5. Optimize S3 Storage Costs
S3 costs add up when you store large volumes of data in the wrong storage class. S3 offers several tiers:
| Storage Class | Use Case | Cost (per GB/month) |
|---|---|---|
| Standard | Frequently accessed | $0.023 |
| Intelligent-Tiering | Unknown access patterns | $0.023 + monitoring fee |
| Standard-IA | Infrequent access (min 30 days) | $0.0125 |
| One Zone-IA | Non-critical infrequent data | $0.01 |
| Glacier Instant Retrieval | Archive with instant access | $0.004 |
| Glacier Flexible | Archive, minutes to hours retrieval | $0.0036 |
| Glacier Deep Archive | Long-term archive, 12-hour retrieval | $0.00099 |
Set up lifecycle policies to automatically transition objects:
{
"Rules": [
{
"ID": "ArchiveOldData",
"Status": "Enabled",
"Filter": {
"Prefix": "logs/"
},
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER_IR"
},
{
"Days": 365,
"StorageClass": "DEEP_ARCHIVE"
}
],
"Expiration": {
"Days": 2555
}
}
]
}
aws s3api put-bucket-lifecycle-configuration \
--bucket my-bucket \
--lifecycle-configuration file://lifecycle.json
For buckets with unpredictable access patterns, S3 Intelligent-Tiering automatically moves objects between tiers based on access frequency. The monitoring fee is $0.0025 per 1,000 objects per month, which pays for itself quickly on large datasets.
6. Use Compute Optimizer for Data-Driven Recommendations
AWS Compute Optimizer analyzes CloudWatch metrics and provides specific recommendations for EC2, EBS, Lambda, and ECS:
# Enable Compute Optimizer
aws compute-optimizer update-enrollment-status --status Active
# Get EC2 recommendations
aws compute-optimizer get-ec2-instance-recommendations \
--query 'instanceRecommendations[].{
Instance:instanceArn,
Current:currentInstanceType,
Recommended:recommendationOptions[0].instanceType,
Savings:recommendationOptions[0].estimatedMonthlySavings.value
}' --output table
Compute Optimizer is free and uses machine learning to analyze 14 days of metrics. It often catches right-sizing opportunities that manual analysis misses, especially for memory and network-bound workloads.
7. Optimize Data Transfer Costs
Data transfer is one of the most overlooked costs on AWS bills. Key rules:
- Inbound traffic is free
- Outbound to internet costs $0.09/GB (first 10 TB)
- Cross-AZ traffic costs $0.01/GB each way
- Cross-region traffic costs $0.02/GB
Reduce data transfer costs by:
# Use VPC endpoints for S3 (eliminates NAT Gateway data charges)
aws ec2 create-vpc-endpoint \
--vpc-id vpc-12345 \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids rtb-12345
# Use CloudFront for content delivery (often cheaper than direct S3)
# CloudFront data transfer is $0.085/GB vs $0.09/GB for S3 direct
For applications with heavy cross-AZ traffic, consider using placement groups or deploying single-AZ for non-critical workloads. Every GB that crosses an AZ boundary costs $0.02 round-trip.
8. Schedule Non-Production Resources
Dev and staging environments do not need to run 24/7. Shutting them down outside business hours saves 65% on those resources:
import boto3
from datetime import datetime
def lambda_handler(event, context):
ec2 = boto3.client('ec2')
action = event.get('action', 'stop')
# Find instances tagged for scheduling
filters = [
{'Name': 'tag:Schedule', 'Values': ['business-hours']},
{'Name': 'instance-state-name',
'Values': ['running'] if action == 'stop' else ['stopped']}
]
response = ec2.describe_instances(Filters=filters)
instance_ids = []
for reservation in response['Reservations']:
for instance in reservation['Instances']:
instance_ids.append(instance['InstanceId'])
if not instance_ids:
print(f"No instances to {action}")
return
if action == 'stop':
ec2.stop_instances(InstanceIds=instance_ids)
print(f"Stopped {len(instance_ids)} instances")
else:
ec2.start_instances(InstanceIds=instance_ids)
print(f"Started {len(instance_ids)} instances")
Trigger this Lambda with two EventBridge rules: one to start at 8 AM and another to stop at 7 PM on weekdays. Do the same for RDS instances using stop-db-instance and start-db-instance.
9. Review and Optimize NAT Gateway Costs
NAT Gateways charge $0.045/hour plus $0.045/GB of data processed. For workloads that make heavy use of AWS services from private subnets, this adds up fast.
Mitigations:
- VPC Gateway Endpoints for S3 and DynamoDB (free, eliminates NAT Gateway data charges for these services)
- VPC Interface Endpoints for other AWS services (charges per hour and per GB, but often cheaper than NAT Gateway for high-volume services)
- NAT instances for dev environments (a t3.micro NAT instance costs $7.50/month vs $32.40/month for a NAT Gateway)
# Check how much data your NAT Gateway processes
aws cloudwatch get-metric-statistics \
--namespace AWS/NATGateway \
--metric-name BytesOutToDestination \
--dimensions Name=NatGatewayId,Value=nat-12345 \
--start-time 2026-06-01T00:00:00Z \
--end-time 2026-07-01T00:00:00Z \
--period 2592000 \
--statistics Sum
If you see tens or hundreds of GB going through NAT to S3, a gateway endpoint will pay for itself immediately since it is free.
10. Set Up Budgets and Alerts
Prevention is cheaper than remediation. AWS Budgets sends alerts when costs exceed thresholds:
aws budgets create-budget \
--account-id 123456789012 \
--budget '{
"BudgetName": "MonthlyBudget",
"BudgetLimit": {
"Amount": "5000",
"Unit": "USD"
},
"TimeUnit": "MONTHLY",
"BudgetType": "COST"
}' \
--notifications-with-subscribers '[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{
"SubscriptionType": "EMAIL",
"Address": "team@example.com"
}
]
},
{
"Notification": {
"NotificationType": "FORECASTED",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 100,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{
"SubscriptionType": "EMAIL",
"Address": "team@example.com"
}
]
}
]'
Create budgets per team or project using cost allocation tags. The forecasted alert is especially valuable since it warns you before the month ends, giving time to investigate and correct course.
Building a Cost Optimization Culture
Tools and automation are important, but lasting cost optimization requires organizational habits:
- Tag everything. Enforce tagging with AWS Organizations SCPs. Without tags, you cannot attribute costs to teams, projects, or environments.
- Review costs weekly. Use Cost Explorer’s daily granularity view to spot anomalies early.
- Include cost in architecture reviews. Before deploying, estimate costs with the AWS Pricing Calculator. Include data transfer in the estimate.
- Assign ownership. Every AWS account or workload should have a cost owner who reviews the bill and acts on recommendations.
Wrapping Up
AWS cost optimization is not a one-time project but an ongoing practice. Start with the quick wins: delete unused resources, right-size instances, and set up budgets. Then tackle the bigger opportunities like Savings Plans, spot instances, and storage lifecycle policies. The ten strategies in this guide can typically reduce an AWS bill by 30-50% without any architectural changes. Review your costs monthly, act on recommendations from Cost Explorer and Compute Optimizer, and make cost awareness part of your engineering culture.
Related articles
- AWS AWS Cost Optimization Strategies That Actually Work
A pragmatic playbook for cutting AWS spend without hurting reliability: right-sizing, savings plans, storage tiering, and architectural moves.
- AWS AWS Lambda Best Practices: Performance, Cost, Security
Master AWS Lambda best practices for optimizing performance, reducing cold starts, controlling costs, and securing your serverless functions in production.
- AWS AWS CDK: Infrastructure in TypeScript Getting Started
Get started with AWS CDK using TypeScript. Define cloud infrastructure with constructs, stacks, and deploy real AWS resources with familiar programming patterns.
- AWS AWS CloudFormation: Infrastructure as Code Guide
Learn AWS CloudFormation from scratch. Build, update, and manage AWS infrastructure with YAML templates, parameters, outputs, and nested stacks.