Right-Sizing EC2 Before It Bites Your Bill
Burstable instances, credit balances, and CloudWatch graphs — how to pick an EC2 size you won't regret, and how to shrink one you already regret.
We ran a t3.medium for our staging environment for fourteen months. CPU sat at 4%. Nobody staged anything because deploys to staging took forever — turns out the burst credits were exhausted and the instance was crawling at 5% of baseline performance. Two problems, one instance, both caused by picking a size based on vibes.
Here's the process I use now, before buying and after.
Before you buy: match the instance to the workload
AWS instance families exist because different workloads bottleneck on different resources:
- T-series (burstable) — general apps with spiky CPU. You earn credits when idle and spend them when busy. Great default for dev/staging, low-traffic sites, jump boxes. Dangerous when the workload is consistently busy — credits drain, performance floor drops.
- M-series (general purpose) — steady mixed CPU/memory. Web servers, app tiers that never idle enough to burst comfortably.
- C-series (compute) — CPU-bound work: build agents, video processing, high-throughput APIs.
- R-series (memory) — caches, in-memory databases, heaps that want room.
- Graviton (anything a) — ARM-based, meaningfully cheaper for the same capability, as long as your stack builds for ARM. Our container images multi-arch now and the savings were immediate.
If the workload is "I don't know yet," start t3.small or t3.medium for anything non-production and watch it for a week before committing.
The three graphs I look at before resizing
In CloudWatch, for the last 7 days:
- CPUUtilization (p95, not average — averages hide spikes). Under 15% p95 for a week suggests you're oversized.
- NetworkIn/NetworkOut — a "CPU-idle" instance that's network-saturated won't get faster with more vCPUs; it needs a different family or a bigger pipe.
- MemoryUtilization — not emitted by default for EC2. Install the CloudWatch agent, seriously. Guessing memory is how you get OOM kills at 3 AM.
For burstable instances specifically, also check CPUCreditBalance and CPUCreditUsage. If the balance sits at zero, you're throttled and either need unlimited mode (which bills when you overshoot), a bigger baseline (t3.large), or a non-burstable family.
Shrinking is where the money is
The uncomfortable truth from cost reviews I've run: most environments are 2–3x oversized because someone picked the instance during an outage and never revisited it.
My shrinking ritual:
- Confirm p95 CPU and memory for 14 days (two weeks covers any monthly job spikes).
- Resize during a maintenance window — stop, change instance type, start. New instance gets a new public IP unless you planned an Elastic IP or it sits behind an ALB — plan for that.
- Keep the old instance stopped (not terminated) for 48 hours. If everything's fine, terminate and reclaim the EBS volume.
aws ec2 stop-instances --instance-ids i-0abc123
aws ec2 modify-instance-attribute \
--instance-id i-0abc123 \
--instance-type '{"Value": "t3.small"}'
aws ec2 start-instances --instance-id i-0abc123
Don't forget what's around the instance
Right-sizing EC2 alone misses half the cost. The EBS volume sized "just in case" at 500 GP3 for a 20 GB filesystem. The unattached volume from an instance that got replaced. The Elastic IP with no instance attached — those bill now. I run this monthly:
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[].{Id:VolumeId,Size:Size}'
Everything in that list is paying rent to do nothing.
The habit that prevents regret
Size decisions get a line in the runbook with the date and the reasoning: "t3.medium, staging, because p95 CPU was 22% at t3.small during load test." Six months later, that note turns a fifteen-minute resize into a five-second decision instead of another fourteen months of 4% CPU.