Post

AWS Cloud Cost Optimization: 10 Architectural Tweaks That Cut EC2, S3, and VPC Bills

A practical FinOps and cloud architecture guide to slashing your monthly AWS bill. Learn how to eliminate NAT gateway data transfer fees, migrate to Graviton, automate S3 lifecycle policies, purge orphan EBS volumes, and negotiate Savings Plans.

AWS Cloud Cost Optimization: 10 Architectural Tweaks That Cut EC2, S3, and VPC Bills

Cloud bills often grow exponentially while actual workload throughput remains static. In AWS, costs rarely explode because of deliberate engineering decisions; they explode due to architectural defaults: idle resources, unmonitored data egress, uncompressed storage tiers, and legacy instance families left running indefinitely.

Implementing FinOps does not mean degrading application performance. By making intentional architectural adjustments across EC2, VPC networking, EBS, and S3, you can routinely reduce your AWS monthly run-rate by 35% to 60%.

Here are the 10 most impactful, production-tested optimizations you should implement today.


1. Eliminate Costly VPC NAT Gateway Surcharges

AWS charges $0.045 per hour per NAT Gateway, plus $0.045 per GB of data processed. If your EC2 instances, EKS pods, or Lambda functions in private subnets communicate with S3, DynamoDB, or ECR, routing that internal AWS traffic through a NAT Gateway is throwing money away.

The Fix: Deploy Free Gateway VPC Endpoints

AWS Gateway Endpoints for Amazon S3 and DynamoDB carry zero hourly cost and zero data processing fees.

You can verify and create an S3 Gateway Endpoint via the AWS CLI:

1
2
3
4
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0123456789abcdef0 \
  --service-name com.amazonaws.us-east-1.s3 \
  --route-table-ids rtb-0123456789abcdef0

Once provisioned, all S3 traffic originating from your private subnets routes over private AWS fiber directly to S3 without touching your NAT Gateway, instantly removing both the data processing charge and NAT bandwidth limits.


2. Migrate x86-64 Workloads to Graviton3 / Graviton4

If your containers and microservices run on interpreted languages (Python, Node.js, Ruby), compiled managed runtimes (Go, Java 17+, .NET 8), or open-source databases (PostgreSQL, Redis, MySQL), running them on legacy Intel (c5, m5, r5) or AMD (c6a, m6a) instances incurs an unnecessary hardware tax.

AWS Graviton (ARM64) instances typically deliver up to 20% lower hourly cost and up to 40% better price-to-performance:

Instance Type (4 vCPU / 16 GiB RAM) Architecture On-Demand Hourly (us-east-1) Monthly Estimate
m5.xlarge (Intel Xeon) x86-64 $0.192 $138.24
m6a.xlarge (AMD EPYC) x86-64 $0.1728 $124.41
m7g.xlarge (AWS Graviton3) ARM64 $0.1632 $117.50
m8g.xlarge (AWS Graviton4) ARM64 $0.1584 $114.04

Docker Multi-Arch Build Pipeline

Compile multi-architecture container images during CI/CD using Docker Buildx so pods can seamlessly run on Graviton nodes:

1
2
3
4
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-service:v1.2.0 \
  --push .

3. Purge Orphaned EBS Volumes and Stale Snapshots

When an EC2 instance is terminated, attached EBS volumes often persist if the DeleteOnTermination flag was false. Over months of auto-scaling and manual debugging, unattached gp2/gp3 volumes accumulate silently, billing monthly block storage at $0.08–$0.10 per GB.

Find and Delete All Unattached EBS Volumes

List all available (unattached) volumes across your primary region:

1
2
3
4
aws ec2 describe-volumes \
  --filters Name=status,Values=available \
  --query "Volumes[*].{ID:VolumeId,Size:Size,Type:VolumeType,Created:CreateTime}" \
  --output table

To automate cleanup, create an AWS Lambda function triggered weekly by an Amazon EventBridge rule that alerts on or removes unattached volumes aged over 7 days.


4. Upgrade Legacy gp2 Storage to gp3

Amazon EBS gp2 volumes tie IOPS directly to provisioned storage capacity (3 IOPS per GB). Consequently, engineers often over-provision drive sizes just to obtain baseline disk performance.

gp3 separates storage volume from IOPS and baseline throughput, delivering 20% lower cost per GB while providing 3,000 baseline IOPS and 125 MB/s throughput for free:

1
2
3
4
# Modify an existing EBS volume to gp3 on-the-fly without downtime
aws ec2 modify-volume \
  --volume-id vol-0123456789abcdef0 \
  --volume-type gp3

5. Implement Automated S3 Lifecycle Rules

Storing terabytes of operational logs, backups, and user uploads indefinitely in S3 Standard ($0.023/GB) leads to compounding bills. Most data is rarely accessed 30 days after creation.

Create an S3 Lifecycle Configuration (lifecycle.json) to automate tiered transitions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "Rules": [
    {
      "ID": "MoveLogsToColdStorage",
      "Status": "Enabled",
      "Filter": {
        "Prefix": "production-logs/"
      },
      "Transitions": [
        {
          "Days": 30,
          "StorageClass": "STANDARD_IA"
        },
        {
          "Days": 90,
          "StorageClass": "GLACIER_IR"
        }
      ],
      "Expiration": {
        "Days": 365
      }
    }
  ]
}

Apply the policy using the AWS CLI:

1
2
3
aws s3api put-bucket-lifecycle-configuration \
  --bucket production-telemetry-bucket-01 \
  --lifecycle-configuration file://lifecycle.json

By shifting from S3 Standard ($0.023/GB) to S3 Glacier Instant Retrieval ($0.004/GB) after 90 days, you achieve an 82.6% storage cost reduction with millisecond retrieval availability.


6. Restrict Cross-Availability Zone Data Transfers

AWS charges $0.01 per GB for data traversing between Availability Zones within the same region (billed once for egress and once for ingress = $0.02/GB round-trip).

In a high-throughput microservices architecture or multi-node database cluster, chatty cross-AZ network traffic can cost thousands of dollars monthly.

Architectural Rules:

  • Topology-Aware Routing in Kubernetes: Enable Topology Aware Hints in Kubernetes Service definitions so traffic prefers staying inside the local availability zone:
    1
    2
    3
    4
    5
    6
    
    apiVersion: v1
    kind: Service
    metadata:
      name: customer-service
      annotations:
        service.kubernetes.io/topology-mode: Auto
    
  • Co-locate Hot Services: Pair backend API workers and in-memory caches (Redis/ElastiCache) in identical AZs where high-concurrency RPCs occur.

7. Compute Savings Plans vs. Standard Reserved Instances

If your infrastructure maintains a baseline 24/7 compute footprint, paying On-Demand rates is leaving money on the table.

Savings Plans Breakdown

  • Compute Savings Plans: Provide up to 66% savings compared to On-Demand rates. They automatically apply regardless of instance family (c6g, m7g, t4g), region, operating system, or tenancy. They also cover AWS Fargate and AWS Lambda execution.
  • EC2 Instance Savings Plans: Offer deeper discounts (up to 72%), but require committing to a specific instance family in a specific region.

Recommendation: If you plan to modernize instance types or adopt Graviton over the coming 12 months, choose a 1-Year, No Upfront or Partial Upfront Compute Savings Plan to retain architectural flexibility while locking in immediate 30–45% savings.


8. Right-Size Instances with AWS Compute Optimizer

Developers routinely over-provision resources out of caution. AWS Compute Optimizer analyzes CloudWatch telemetry (CPU utilization, memory usage via CloudWatch Agent, disk I/O) using machine learning to surface actionable downsizing opportunities.

Enable Compute Optimizer across your AWS Organization:

1
aws compute-optimizer update-enrollment-status --status Active

Query top under-utilized EC2 instances via CLI:

1
2
3
aws compute-optimizer get-ec2-instance-recommendations \
  --query "instanceRecommendations[?finding=='Overprovisioned'].{ID:instanceArn,Current:currentInstanceType,Recommended:recommendationOptions[0].instanceType}" \
  --output table

9. Switch Unpredictable Dev/Staging Databases to Aurora Serverless v2

Running provisioned Amazon RDS Multi-AZ instances 24/7 for development, staging, or QA environments incurs continuous charges even during weekends and overnight hours.

  • For Non-Production Environments: Implement automated startup/shutdown scripts via AWS Systems Manager Automation to stop non-prod RDS instances outside business hours (cutting dev database bills by ~60%).
  • For Variable Production Workloads: Use Aurora Serverless v2, which scales compute capacity up and down in fractions of an ACU (Aurora Capacity Unit: 2 GiB RAM + CPU equivalent) dynamically in milliseconds, matching true query demand without pre-provisioning peak headroom.

10. Implement Guardrails: Anomaly Detection and Budgets

Optimization is not a one-time project; it requires continuous guardrails to prevent accidental cost regressions.

Deploy AWS Cost Anomaly Detection

AWS Cost Anomaly Detection uses machine learning to identify unexpected billing surges (such as an unconstrained Lambda infinite loop or runaway Athena query):

1
2
aws ce create-anomaly-monitor \
  --anomaly-monitor '{"MonitorName":"RootAccountDailyMonitor","MonitorType":"DIMENSIONAL","MonitorDimension":"SERVICE"}'

Pair this with an AWS Budget configured to alert via SNS to your engineering Slack channel whenever actual or forecasted spend exceeds 90% of your target monthly allocation.

This post is licensed under CC BY 4.0 by the author.