Migrate gp2 EBS volumes to gp3 and cut storage costs about 20%

There is a line item on your AWS bill that has been quietly overcharging you for years. It is your gp2 EBS volumes. The fix is a one-command change that runs while your instances stay online. Most teams never get to it because it sounds risky and nobody owns the storage tier. Let me walk you through the whole thing.

Why gp3 is almost always cheaper than gp2

gp3 is the newer general-purpose SSD volume type. It lists at about $0.08 per GB-month in most regions. gp2 lists at about $0.10 per GB-month. That is roughly 20% off the same storage, before you change anything else.

The reason the discount is close to free is performance. gp2 ties its baseline IOPS to volume size, at 3 IOPS per GB. gp3 ships a flat baseline of 3000 IOPS and 125 MB/s of throughput at no extra cost, regardless of size.

Walk through a 500 GiB volume. On gp2, 500 GiB times 3 IOPS gives you a 1500 IOPS baseline. On gp3, that same volume starts at 3000 IOPS. You pay less and get twice the baseline performance.

So for the common case, a smaller attached volume serving a database or an app server, gp3 is a clean win. Lower bill, equal or better floor on IOPS. The migration is online, which we will get to.

The 1000 GiB trap

There is one place this logic flips, and it is the part most write-ups skip.

gp2 hands you 3 IOPS per GB of baseline. At exactly 1000 GiB, that is 3000 IOPS, which is the same number gp3 gives you by default. Above 1000 GiB, gp2's baseline keeps climbing while gp3's default stays parked at 3000.

A 4000 GiB gp2 volume carries a 12000 IOPS baseline. Move it to gp3 with the defaults and you drop to 3000 IOPS. For a busy volume that is a real performance regression, and you will feel it before you see the savings.

The trap has a fix. gp3 lets you provision IOPS and throughput on top of the baseline, and the first 3000 IOPS and 125 MB/s are already included. You buy back only the difference. For a 4000 GiB volume you would provision an extra 9000 IOPS to match the old baseline, then do the math on whether the storage discount still beats the provisioned-IOPS cost.

Often the volume never used that headroom in the first place. Pull the CloudWatch VolumeReadOps and VolumeWriteOps metrics for the last few weeks before you decide. Size IOPS to what the volume actually does, not to what gp2 happened to give it for free.

Find your gp2 candidates by hand

Start by listing every gp2 volume in the region. The AWS CLI does this in one call.

aws ec2 describe-volumes \
  --filters Name=volume-type,Values=gp2 \
  --query "Volumes[].{id:VolumeId,size:Size,az:AvailabilityZone,state:State}" \
  --output table

That gives you the inventory. Now split it on the 1000 GiB line, because that is where the decision changes.

# Clean wins: under 1000 GiB, gp3 is cheaper at equal-or-better baseline IOPS.
aws ec2 describe-volumes \
  --filters Name=volume-type,Values=gp2 \
  --query "Volumes[?Size < \`1000\`].{id:VolumeId,size:Size,az:AvailabilityZone}" \
  --output table

# Check carefully: at or above 1000 GiB, confirm real IOPS usage before moving.
aws ec2 describe-volumes \
  --filters Name=volume-type,Values=gp2 \
  --query "Volumes[?Size >= \`1000\`].{id:VolumeId,size:Size,az:AvailabilityZone}" \
  --output table

The first list is your easy pile. Move those and bank the 20% with no performance question to answer. The second list is the one you check against CloudWatch first, sizing gp3 IOPS to actual usage.

Run this in every region you operate in. EBS is regional, and a forgotten volume in a region you rarely touch is still billing you every hour.

Migrate online with no downtime

Here is the part that surprises people. You do not detach the volume, snapshot it, or stop the instance. You modify the live volume in place.

aws ec2 modify-volume \
  --volume-id vol-0123456789abcdef0 \
  --volume-type gp3

The volume enters the optimizing state and stays fully readable and writable the whole time. Your database keeps serving queries. Your app keeps taking traffic. AWS does the conversion in the background.

For a volume at or above 1000 GiB, add the IOPS and throughput you decided on in the same command.

aws ec2 modify-volume \
  --volume-id vol-0123456789abcdef0 \
  --volume-type gp3 \
  --iops 12000 \
  --throughput 250

Watch the modification move through its states.

aws ec2 describe-volumes-modifications \
  --volume-id vol-0123456789abcdef0 \
  --query "VolumesModifications[].{state:ModificationState,progress:Progress}" \
  --output table

ModificationState walks from modifying to optimizing to completed. The volume is already gp3 and already cheaper once it reaches optimizing. The optimizing phase is AWS settling the performance characteristics, and it does not block your workload.

If something looks wrong, you roll back the same way you rolled forward. Modify the volume back to gp2.

aws ec2 modify-volume \
  --volume-id vol-0123456789abcdef0 \
  --volume-type gp2

One guardrail to plan around. A single volume can only be modified once every 6 hours. So test on one representative volume, confirm the application is happy, then roll the change out across the easy pile. Do not fire off a modify and a rollback back to back, because the second call will be refused until the window clears.

A quick decision tree

graph TD
  A[gp2 volume] --> B{Size >= 1000 GiB?}
  B -- No --> C[Modify to gp3: same or better baseline, about 20% cheaper]
  B -- Yes --> D{CloudWatch IOPS usage near the gp2 baseline?}
  D -- No --> E[Modify to gp3 with default 3000 IOPS]
  D -- Yes --> F[Modify to gp3 and provision IOPS to match, then recheck the math]

How OhChimp approaches this

Doing this by hand works. The catch is that it is a standing chore, and the gp2 volumes that cost you the most are the ones nobody remembers to check.

OhChimp finds the gp2 volumes across your AWS accounts and regions, including the ones in the corners you forgot about. It writes a reviewable gp2-to-gp3 plan with a confidence score, a risk level, and the rollback steps spelled out. The 1000 GiB question is already answered for each volume, so you are reading a recommendation, not redoing the analysis.

You read the plan and click apply. Nothing changes until you do. After the change ships, the saving is checked against your real AWS bill, so you see the dollars that actually left, measured on the invoice.

See how the AWS connection works on the AWS integration page.