Skip to main content
emnode / learn
Cost

Migrate EBS volumes from gp2 to gp3

Same baseline performance, ~20% cheaper, online change — the EBS upgrade with no downside.

15 min·10 sections·AWS

Last reviewed

gp2 vs gp3: the basics

What does it mean to migrate an EBS volume from gp2 to gp3?

gp2 is the original general-purpose SSD on EBS. Its defining quirk is that IOPS are tied to volume size: you get 3 IOPS per GB, with a floor of 100 and a ceiling of 16,000 — so a 100 GB gp2 gets 300 baseline IOPS, and the only way to buy more is to grow the volume. Throughput scales similarly. Small volumes also lean on a burst-credit bucket to reach 3,000 IOPS for short periods, which papers over the limitation for spiky workloads but fails the moment credits run out.

gp3 is the replacement AWS shipped in late 2020. It charges ~$0.08/GB-month (vs ~$0.10 for gp2), bundles 3,000 baseline IOPS and 125 MiB/s baseline throughput at no extra cost, and lets you provision IOPS up to 16,000 and throughput up to 1,000 MiB/s independently of size. The performance ceiling is identical to gp2 at the top end; the floor is dramatically higher.

Cost Optimization Hub and most FinOps scanners flag every remaining gp2 volume as a migration candidate. The 20% per-GB saving is real, immediate, and stacks with the fact that most gp2 fleets are oversized purely to chase IOPS — once you're on gp3 you can often shrink the volume too. There's no Reserved Instance equivalent for EBS, no commitment to break, no snapshot to take. The migration is online.

In this lesson you'll learn why gp3 is the default for new EBS volumes, when (rarely) you'd still pick gp2 or io1/io2, and how to migrate a live fleet without downtime. You'll see the modify-volume API at work, the optimizing state, and the bulk-migration pattern most teams use to convert thousands of volumes in a single afternoon.

Fun fact

AWS made the migration deliberately frictionless

When gp3 launched, AWS published a re:Invent blog post showing the migration as a single CLI call against a live volume — no detach, no snapshot, no reboot. They knew most customers would never migrate if it required a maintenance window. The bet paid off: gp3 became the default for new volumes within 18 months, and AWS quietly retired the gp2 default on new instance launches in many regions. If you still have a gp2 fleet in 2026, you're paying 20% extra for the privilege of inertia.

gp2 → gp3 in action

Nina runs cloud platform at a fintech with ~1,200 EC2 instances across three regions. A FinOps review flags $9,400/month sitting in gp2 volumes — 14 TB of root disks and data volumes across the fleet, untouched since the original lift-and-shift in 2022.

She pulls the inventory: every volume is gp2, the median size is 100 GB, and CloudWatch shows that only ~40 of the 1,200 volumes ever touch 3,000 IOPS. The rest spend their lives well below the baseline that gp3 ships with for free.

She drafts a migration plan that converts every gp2 volume in the account to gp3 at default settings (3,000 IOPS, 125 MiB/s) — a flat 20% drop on the per-GB rate, with no perceptible performance change. Projected monthly savings: ~$1,880, recurring forever.

First, list every gp2 volume in the region and its size so we can spot any outliers before the bulk run.

$ aws ec2 describe-volumes --filters Name=volume-type,Values=gp2 --query 'Volumes[].[VolumeId,Size,Iops,State]' --output table
------------------------------------------------------
| DescribeVolumes |
+----------------------+------+------+--------------+
| vol-0a1b2c3d4e5f60001 | 100 | 300 | in-use |
| vol-0a1b2c3d4e5f60002 | 15 | 100 | in-use |
| vol-0a1b2c3d4e5f60003 | 500 | 1500 | in-use |
| vol-0a1b2c3d4e5f60004 | 2000 | 6000 | in-use |
| vol-0a1b2c3d4e5f60005 | 200 | 600 | in-use |
+----------------------+------+------+--------------+
# 1,196 more rows. The 15 GB volume jumps out — 100 IOPS floor, paying 20% premium for nothing.

Inventory pass — gp2 volumes with their current size and IOPS floor.

Now flip one volume to gp3. The call returns immediately; the volume keeps serving traffic the entire time.

$ aws ec2 modify-volume --volume-id vol-0a1b2c3d4e5f60002 --volume-type gp3
{
"VolumeModification": {
"VolumeId": "vol-0a1b2c3d4e5f60002",
"ModificationState": "modifying",
"OriginalVolumeType": "gp2",
"OriginalIops": 100,
"TargetVolumeType": "gp3",
"TargetIops": 3000,
"TargetThroughput": 125,
"Progress": 0
}
}
# Billing flips to gp3 rate immediately. State will move to 'optimizing' within seconds, then 'completed'.

Live volume modification — no detach, no snapshot, no reboot. The 15 GB volume now has 30× the baseline IOPS for 20% less.

What happens at the API layerdeep dive

ModifyVolume is an asynchronous, online operation on the EBS data plane. AWS does not move your data — gp2 and gp3 share the same underlying SSD substrate. What changes is the metering record on the volume and the IOPS/throughput contract the storage layer enforces. Because there's no physical relocation, the call returns in under a second and the volume stays attached and writable throughout.

Pricing changes at the moment the API call is accepted, not when the migration finishes. The volume moves through three modification states: modifying (request accepted, contracts being applied), optimizing (background work to align the volume's performance envelope with the new type), and completed. You'll see the volume sit in optimizing for anywhere from a few minutes to 24 hours depending on size — and for that whole window the volume serves I/O normally and bills at the gp3 rate.

The one constraint worth knowing: AWS rate-limits modifications to one per volume per six hours. If you fat-finger the IOPS or throughput, you cannot immediately undo the change. Plan the migration once, validate on a single volume, then sweep the fleet.

# Track the migration state for a volume that's currently 'optimizing'.
aws ec2 describe-volumes-modifications \
  --volume-ids vol-0a1b2c3d4e5f60002 \
  --query 'VolumesModifications[0].[ModificationState,Progress,TargetVolumeType,StartTime]' \
  --output table

# Sweep the fleet — gp2 → gp3 at default 3000 IOPS / 125 MiB/s for every in-use gp2 volume.
aws ec2 describe-volumes \
  --filters Name=volume-type,Values=gp2 \
  --query 'Volumes[].VolumeId' --output text \
  | tr '\t' '\n' \
  | xargs -n1 -P4 -I{} aws ec2 modify-volume --volume-id {} --volume-type gp3

What's the impact of staying on gp2?

The direct hit is ~20% on storage rate. A 10 TB gp2 footprint costs $1,024/month at $0.10/GB; the same on gp3 is $819 — $205/month, $2,460/year, recurring on a line item nobody is monitoring. Scale that to a Fortune 500 with petabytes of EBS and the number lands in the seven figures.

The hidden second-order cost is volume oversizing. Because gp2 IOPS scale with size, teams routinely provision 1 TB volumes for workloads that need 200 GB of capacity — the extra 800 GB exists purely to buy IOPS headroom. On gp3 that 200 GB volume gets the same 3,000 IOPS for free, and you can shrink the on-disk footprint at the next maintenance window. The combined savings (rate + shrink) frequently approaches 40%, not 20%.

There's also a tail risk on gp2: burst-credit exhaustion. Small gp2 volumes (< 1 TB) rely on a credit bucket to reach 3,000 IOPS under load. When credits drain, performance falls back to the size-based floor — 100 IOPS on a 33 GB volume, for instance. Workloads that have been "fine for years" can suddenly degrade under an unusual traffic pattern. gp3's baseline isn't burstable; it's the floor, always available.

Finally: gp2 is on a long-term glide path to deprecation. AWS has not announced an end-of-life date, but every new feature (Snapshot Archive, EBS direct APIs, encryption defaults) ships gp3-first and gp2-later. Staying on gp2 means staying on the side of EBS that AWS is no longer investing in.

How do you migrate safely?

The mechanics are trivial; the discipline is in inventory, validation, and not blowing past the 6-hour cooldown. Run the four-step loop below and you'll convert any fleet without an outage.

1. Inventory every gp2 volume in every region

Use describe-volumes with Name=volume-type,Values=gp2 and dump the result with size, IOPS, attached instance, and tags. Look for outliers: any volume with IOPS > 3,000 (these are large gp2 volumes that may need provisioned IOPS on gp3 to maintain parity), any io1/io2 volumes in the same fleet (different conversation), and any unattached volumes (those should be deleted, not migrated).

2. Pilot on one non-critical volume

Pick a dev or staging volume, run modify-volume --volume-type gp3 against it, watch it move through modifying → optimizing → completed. Confirm the attached workload sees no I/O errors and CloudWatch's VolumeQueueLength stays in the expected range. The whole pilot takes less than an hour.

3. Sweep the fleet — defaults are usually right

For volumes currently below 3,000 IOPS, default gp3 settings (3,000 IOPS, 125 MiB/s) match or exceed the existing performance envelope. For larger gp2 volumes where IOPS = 3 × size (e.g. a 2 TB gp2 with 6,000 IOPS), pass --iops and --throughput explicitly on the modify call so you don't accidentally regress performance. Parallelise with xargs -P but stay under your account's volume-modification rate limit.

4. Track via DescribeVolumesModifications and verify the cost line

Don't trust the migration finished just because the API accepted the call. Poll describe-volumes-modifications until every volume reads completed, then check the Cost Explorer EBS usage line the next day — you should see the gp2 GB-month figure dropping and the gp3 line rising by the same volume. If anything stays in modifying for more than an hour, open a support ticket; it's not a normal state to linger in.

# Bulk migration with explicit IOPS preservation for large volumes.
aws ec2 describe-volumes \
  --filters Name=volume-type,Values=gp2 \
  --query 'Volumes[].[VolumeId,Size,Iops]' --output text \
  | while read VOL SIZE IOPS; do
      # For gp2 volumes whose IOPS exceeds the gp3 default, preserve them explicitly.
      if [ "$IOPS" -gt 3000 ]; then
        aws ec2 modify-volume --volume-id "$VOL" --volume-type gp3 --iops "$IOPS" --throughput 250
      else
        aws ec2 modify-volume --volume-id "$VOL" --volume-type gp3
      fi
    done

# Watch the fleet finish optimizing.
aws ec2 describe-volumes-modifications \
  --filters Name=modification-state,Values=optimizing,modifying \
  --query 'VolumesModifications[].[VolumeId,ModificationState,Progress]' \
  --output table

Quick quiz

Question 1 of 5

You have a 200 GB gp2 volume on a production database. It currently has 600 IOPS (3 per GB) and you've never seen IOPS pressure in CloudWatch. What's the right move?

You've completed Migrate EBS volumes from gp2 to gp3. You now know the pricing math, the IOPS-decoupling story, and the four-step loop — inventory, pilot, sweep, verify — to convert a fleet of any size without downtime. The next time a scanner surfaces a gp2 finding, you'll know it's almost always a same-day fix with money on the table.

Back to the library