AWS Cloud Cost Optimization 2026 — Cut Your Bill by 60% Without Killing Performance

Sanjeev SharmaSanjeev Sharma
7 min read

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=Environment

Send 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%:

OptionFlexibilityMax SavingsBest For
On-DemandFull0%Unknown workloads
Compute Savings PlansEC2, Lambda, Fargate66%Most workloads
EC2 Instance Savings PlansSpecific family + region72%Stable instance families
Standard Reserved InstancesSpecific type + AZ75%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-31

Recommendation: 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.large

Right-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 Average

S3 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

OptimizationTypical SavingsEffort
Delete unattached EBS volumes$0.08/GB/monthLow
Release unused Elastic IPs$3.65/IP/monthLow
Delete unused Load Balancers$16/month eachLow
Right-size oversized EC220-60%Medium
Spot Instances for batch/stateless70-90%Medium
Savings Plans 1-year no-upfront40-66%Low
S3 Intelligent-Tiering40-70% on old dataLow
Stop dev/staging RDS after hours60-100% on devMedium
Replace NAT Gateway with VPC EndpointsVariesMedium
CloudFront in front of S3 (cut GET costs)VariesLow
# 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-db

FinOps: 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

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading