AWS Cloud Cost Optimization 2026 — Cut Your Bill by 60% Without Killing Performance
Advertisement
Introduction
Why This Matters
The average company wastes 32% of its cloud spend. AWS bills grow silently — dev environments left running, oversized instances, unused Elastic IPs, and data-transfer charges nobody noticed. Teams that treat cloud spend as a team metric rather than an ops afterthought cut their bills by 40-60% without reducing performance. This guide covers the highest-impact optimizations available in 2026.
Cost Visibility: Know Before You Optimize
# Enable Cost Explorer (free)
# AWS Console > Billing > Cost Explorer > Enable
# Get monthly costs by service via CLI
aws ce get-cost-and-usage \
--time-period Start=2026-03-01,End=2026-03-31 \
--granularity MONTHLY \
--metrics "UnblendedCost" \
--group-by Type=DIMENSION,Key=SERVICE \
--query 'ResultsByTime[0].Groups[*].{Service:Keys[0],Cost:Metrics.UnblendedCost.Amount}' \
--output table
# Get costs by environment tag
aws ce get-cost-and-usage \
--time-period Start=2026-03-01,End=2026-03-31 \
--granularity MONTHLY \
--metrics "UnblendedCost" \
--group-by Type=TAG,Key=EnvironmentSend weekly cost reports to Slack:
// cost-reporter.ts
import { CostExplorerClient, GetCostAndUsageCommand } from '@aws-sdk/client-cost-explorer'
const client = new CostExplorerClient({ region: 'us-east-1' })
async function getWeeklyCosts(): Promise<Record<string, number>> {
const end = new Date()
const start = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
const { ResultsByTime } = await client.send(new GetCostAndUsageCommand({
TimePeriod: {
Start: start.toISOString().split('T')[0],
End: end.toISOString().split('T')[0],
},
Granularity: 'DAILY',
Metrics: ['UnblendedCost'],
GroupBy: [{ Type: 'DIMENSION', Key: 'SERVICE' }],
}))
const totals: Record<string, number> = {}
ResultsByTime?.forEach(day => {
day.Groups?.forEach(group => {
const service = group.Keys?.[0] ?? 'Unknown'
const cost = parseFloat(group.Metrics?.UnblendedCost?.Amount ?? '0')
totals[service] = (totals[service] ?? 0) + cost
})
})
return totals
}Reserved Instances vs Savings Plans
Commit to usage for 1-3 years and save 40-75%:
| Option | Flexibility | Max Savings | Best For |
|---|---|---|---|
| On-Demand | Full | 0% | Unknown workloads |
| Compute Savings Plans | EC2, Lambda, Fargate | 66% | Most workloads |
| EC2 Instance Savings Plans | Specific family + region | 72% | Stable instance families |
| Standard Reserved Instances | Specific type + AZ | 75% | Fixed, predictable workloads |
# Get 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 THIRTY_DAYS
# Check current Savings Plans utilization
aws ce get-savings-plans-utilization \
--time-period Start=2026-03-01,End=2026-03-31Recommendation: buy Compute Savings Plans at 1-year, no-upfront to cover 70% of your baseline. Let the remaining 30% be On-Demand to absorb spikes.
Spot Instances: 70-90% Off
Spot Instances use spare AWS capacity at massive discounts. Handle interruptions gracefully:
// spot-handler.ts — Handle 2-minute Spot interruption notice
import http from 'http'
async function checkSpotInterruption(): Promise<boolean> {
return new Promise((resolve) => {
const req = http.get(
'http://169.254.169.254/latest/meta-data/spot/termination-time',
(res) => resolve(res.statusCode === 200)
)
req.on('error', () => resolve(false))
req.setTimeout(1000, () => { req.destroy(); resolve(false) })
})
}
async function gracefulShutdown() {
console.log('Spot interruption — draining queue and shutting down')
isShuttingDown = true
await drainCurrentWork()
await deregisterFromLoadBalancer()
await db.end()
process.exit(0)
}
setInterval(async () => {
if (await checkSpotInterruption()) {
await gracefulShutdown()
}
}, 5000)# CloudFormation ASG with Spot + On-Demand mix
MixedInstancesPolicy:
InstancesDistribution:
OnDemandBaseCapacity: 1
OnDemandPercentageAboveBaseCapacity: 20
SpotAllocationStrategy: capacity-optimized
LaunchTemplate:
Overrides:
- InstanceType: t3.large
- InstanceType: t3a.large
- InstanceType: m5.largeRight-Sizing EC2 Instances
Oversized EC2 instances are the number one source of waste:
# Enable Compute Optimizer (free)
aws compute-optimizer update-enrollment-status --status Active
# Get right-sizing recommendations
aws compute-optimizer get-ec2-instance-recommendations \
--query 'instanceRecommendations[*].{
Instance:instanceArn,
CurrentType:currentInstanceType,
Recommended:recommendationOptions[0].instanceType,
MonthlySavings:recommendationOptions[0].estimatedMonthlySavings.value
}' \
--output table
# Find instances with less than 5% average CPU over 14 days
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-xxx \
--start-time 2026-03-01T00:00:00Z \
--end-time 2026-03-26T00:00:00Z \
--period 86400 \
--statistics AverageS3 Cost Optimization
# Enable S3 Intelligent-Tiering for unpredictable access patterns
aws s3api put-bucket-intelligent-tiering-configuration \
--bucket my-bucket \
--id EntireBucket \
--intelligent-tiering-configuration '{
"Id": "EntireBucket",
"Status": "Enabled",
"Tierings": [
{"Days": 90, "AccessTier": "ARCHIVE_ACCESS"},
{"Days": 180, "AccessTier": "DEEP_ARCHIVE_ACCESS"}
]
}'S3 lifecycle policy:
{
"Rules": [{
"Status": "Enabled",
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER_IR" },
{ "Days": 365, "StorageClass": "DEEP_ARCHIVE" }
],
"NoncurrentVersionExpiration": { "NoncurrentDays": 90 },
"Expiration": { "Days": 2555 }
}]
}Quick Wins Checklist
| Optimization | Typical Savings | Effort |
|---|---|---|
| Delete unattached EBS volumes | $0.08/GB/month | Low |
| Release unused Elastic IPs | $3.65/IP/month | Low |
| Delete unused Load Balancers | $16/month each | Low |
| Right-size oversized EC2 | 20-60% | Medium |
| Spot Instances for batch/stateless | 70-90% | Medium |
| Savings Plans 1-year no-upfront | 40-66% | Low |
| S3 Intelligent-Tiering | 40-70% on old data | Low |
| Stop dev/staging RDS after hours | 60-100% on dev | Medium |
| Replace NAT Gateway with VPC Endpoints | Varies | Medium |
| CloudFront in front of S3 (cut GET costs) | Varies | Low |
# Find and list unattached EBS volumes
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[*].{ID:VolumeId,Size:Size,Type:VolumeType}' \
--output table
# Find unused Elastic IPs
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==`null`].{IP:PublicIp,ID:AllocationId}' \
--output table
# Stop dev RDS instances (saves 100% while stopped — max 7 days)
aws rds stop-db-instance --db-instance-identifier dev-dbFinOps: Cost as a Team Practice
# Create a budget alert at 80% of monthly target
aws budgets create-budget \
--account-id 123456789012 \
--budget '{
"BudgetName": "MonthlyBudget",
"BudgetLimit": {"Amount": "500", "Unit": "USD"},
"TimeUnit": "MONTHLY",
"BudgetType": "COST"
}' \
--notifications-with-subscribers '[{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80
},
"Subscribers": [
{"SubscriptionType": "EMAIL", "Address": "devops@company.com"}
]
}]'Common Mistakes
- No resource tagging — without Environment, Team, and Project tags, you cannot see which team is causing cost spikes
- Single Spot Instance type in ASG — specify 4-6 instance types so AWS always has a Spot pool available
- Keeping dev RDS instances running 24/7 — stop them outside business hours to save 60-100% on dev database costs
- Forgetting NAT Gateway costs — NAT Gateways charge per GB processed; use VPC Endpoints for S3 and DynamoDB to eliminate this cost
- No budget alerts — silent cost overruns are common; always set alerts at 80% and 100% of expected monthly spend
Best Practices
- Tag every resource at creation — enforce tagging with AWS Config rules and Service Control Policies
- Run weekly cost reviews as a team ritual; make cloud spend a shared KPI, not an ops-only concern
- Use Aurora Serverless v2 for dev/staging databases — scales to near-zero when idle, full speed when needed
- Enable AWS Trusted Advisor in Business or Enterprise support tier for automated right-sizing and cost recommendations
- Measure cost per feature — track Lambda invocations and RDS costs per team to create accountability
Key Takeaways
- The average company wastes 32% of cloud spend; most waste comes from oversized instances, idle resources, and missing commitments
- Compute Savings Plans at 1-year, no-upfront provide 40-66% savings on EC2, Lambda, and Fargate with full flexibility
- Spot Instances save 70-90% over On-Demand for fault-tolerant workloads; handle the 2-minute interruption notice with graceful shutdown
- AWS Compute Optimizer identifies idle and oversized EC2 instances for free; check recommendations monthly
- S3 lifecycle policies automatically move objects to cheaper storage classes as they age — set them up once and save continuously
- Unattached EBS volumes, unused Elastic IPs, and idle Load Balancers are pure waste; run the cleanup scripts quarterly
- Budget alerts at 80% and 100% of monthly target are mandatory — cost surprises should never appear in the monthly invoice
- FinOps is a culture, not a tool — teams that review cloud spend weekly ship features at lower cost than teams that audit monthly
Advertisement