Changing an EC2 instance type is one of those operations that looks trivial in the console (stop, edit, start) and yet bites people in three places: an instance-store volume that evaporates the moment you stop the instance, a Xen-era AMI that refuses to boot on a Nitro host, and a production fleet that takes the wrong kind of downtime because nobody set up an Auto Scaling Group with a rolling replace. This post is the full procedure for an EBS-backed instance, the CLI one-liners, the compatibility rules that decide whether the resize succeeds or fails, and the zero-downtime pattern I reach for on anything customer-facing.
How do I change an AWS EC2 instance type?
For an EBS-backed instance the procedure is three calls. Stop the instance: aws ec2 stop-instances --instance-ids i-0abc. Modify the type: aws ec2 modify-instance-attribute --instance-id i-0abc --instance-type '{"Value": "t3.large"}'. Start it again: aws ec2 start-instances --instance-ids i-0abc. The whole thing takes under a minute and the EBS root volume, attached EBS data volumes, security groups, IAM role, and Elastic IP all survive. The instance ID stays the same; the public IPv4 will change unless you use an Elastic IP. Instance-store volumes are wiped on stop and the resize requires migration to a fresh instance (snapshot the EBS root to an AMI, launch a new instance from the AMI on the new type). The destination type must be compatible with the AMI's architecture (x86_64 vs arm64), virtualization (HVM vs PV), network driver (ENA), and storage driver (NVMe). Modern Nitro families (C5/M5/R5/T3 and later) require ENA + NVMe; older Xen families do not. Moving from Xen to Nitro on an AMI that lacks those drivers fails at boot.
Jump to:
- Before you start: the four pre-flight checks
- Method 1: Stop, modify, start (the standard path)
- Method 2: Console click-path
- Compatibility: what can move to what
- Method 3: Snapshot AMI + relaunch (Xen to Nitro, instance-store)
- Method 4: ASG rolling replace (zero downtime in production)
- Cost angle: vertical scale vs gp3 + smaller box
- Stop vs Hibernate vs Terminate
- Comparison: four ways to change an instance type
- Common pitfalls
- What to do next
- FAQ
Before you start: the four pre-flight checks
Four things to confirm before you touch the instance, in increasing order of how badly people get burned by missing them.
1. The root volume is EBS, not instance store. Instance store volumes are ephemeral local SSDs physically attached to the host. Stopping an instance-store-backed instance wipes the volume permanently. Check with:
aws ec2 describe-instances --region :region --instance-ids :instance_id \
--query 'Reservations[].Instances[].RootDeviceType'ebs is fine. instance-store means the standard stop-modify-start path is not available; see Method 3.
2. Anything important on the instance store is backed up. Even on EBS-rooted instances, larger families (m5d, r5d, i3, c5d) come with instance-store volumes attached as /dev/nvme1n1, /dev/nvme2n1, etc. Anything on those volumes is gone when you stop the instance. Snapshot or rsync the data first.
3. The destination instance type is in the same family architecture. x86_64 AMIs cannot boot on arm64 (Graviton) instances and vice versa. Check the AMI's architecture:
aws ec2 describe-images --region :region --image-ids ami-0abc \
--query 'Images[].Architecture'If you want to move from x86 to Graviton (or vice versa), you have to rebuild the image on the target architecture and migrate, not resize.
4. The AMI has the drivers the destination type requires. Nitro instance types (C5, M5, R5, T3, M6, C6, R6, M7, C7, and every newer family) require both ENA (network) and NVMe (block storage) drivers in the AMI. AMIs built before 2018 or based on legacy distributions may lack them. Check:
aws ec2 describe-instances --region :region --instance-ids :instance_id \
--query 'Reservations[].Instances[].EnaSupport'true means the network driver is loaded. For NVMe, run lsmod | grep nvme inside the instance before stopping it. If either is missing, the instance starts on the new type but fails to attach storage or come up on the network; you get a failed status check and have to revert.
Take a snapshot of the EBS root before you do anything destructive:
aws ec2 create-snapshot --region :region --volume-id vol-0abc \
--description "pre-resize-$(date +%Y%m%d-%H%M)"Snapshots are incremental, cost cents, and complete in seconds for a typical root volume. The only single-command rollback you have.
Method 1: Stop, modify, start (the standard path)
The three CLI calls, in order:
aws ec2 stop-instances --region :region --instance-ids :instance_idWait for the state to settle:
aws ec2 wait instance-stopped --region :region --instance-ids :instance_idModify the type:
aws ec2 modify-instance-attribute \
--region :region \
--instance-id :instance_id \
--instance-type '{"Value": ":new_type"}'The JSON quoting is awkward but exact. The CLI accepts the shorthand --instance-type t3.large on many recent versions too, but the documented form is the JSON Value wrapper and it works on every CLI v1 and v2 release since 2013.
Start the instance again:
aws ec2 start-instances --region :region --instance-ids :instance_id
aws ec2 wait instance-running --region :region --instance-ids :instance_idVerify the new type:
aws ec2 describe-instances --region :region --instance-ids :instance_id \
--query 'Reservations[].Instances[].InstanceType'Total wall-clock time on a typical EBS-rooted instance: 60 to 90 seconds. The instance ID is unchanged. The Elastic IP, if any, reattaches automatically. EBS volumes stay attached at the same device paths. Security groups, IAM instance profile, key pair, user data, and tags all persist.
If the destination type is not compatible (Xen-only AMI on a Nitro family, or a c5n that requires ENA when the AMI lacks it) start-instances returns the instance to running but the status check fails. Revert with the same sequence in reverse: stop, modify back to the original type, start.
Method 2: Console click-path
For the rare one-off where you don't want to type the CLI. EC2 → Instances → select the instance → Instance state → Stop instance. After it shows Stopped, Actions → Instance settings → Change instance type. Pick the new type from the dropdown. Save. Instance state → Start instance.
The console enforces the same compatibility rules as the CLI and refuses incompatible types with a slightly more readable error. Useful for exploring what types are available in the current region and AZ.
Compatibility: what can move to what
Not every type can move to every other type without rebuilding the AMI. The rules:
| Constraint | Rule | What happens if violated |
|---|---|---|
| AMI architecture | x86_64 → x86_64, arm64 → arm64 | Instance fails to boot; status check 1/2 fail |
| Virtualization | HVM → HVM only (PV is end-of-life) | New type rejects the AMI at launch |
| ENA support | Required for c5, m5, r5, t3, m6, c6, r6, and every newer family | Instance starts but has no network |
| NVMe support | Required for Nitro families (same list as ENA) | Instance starts but cannot mount root EBS |
| Tenancy | Dedicated host families cannot move to shared tenancy and vice versa | API error before stop completes |
| Placement group | Cluster placement requires compatible types (10 Gbps capable) | API error on start |
| Family-specific features | EFA (HPC), Local Zones, Wavelength | Validation error on modify |
The good cases: same family generation (t3.medium → t3.large), within a Nitro generation across families (m5.large → c5.large → r5.large), bumping a generation within an architecture (m5.large → m6i.large). The bad cases: old Xen family (m3, c3, m4, c4) to Nitro family on an AMI without ENA / NVMe; x86 to arm64 without rebuilding the image.
The safe play for any non-trivial jump is the snapshot-AMI-and-relaunch path in Method 3. It costs ten extra minutes but eliminates the "instance boots but has no network" failure mode.
Method 3: Snapshot AMI + relaunch (Xen to Nitro, instance-store)
When the destination type isn't compatible with the current AMI, the fix is to bake a new AMI from the running instance with ENA and NVMe enabled, then launch a fresh instance from that AMI on the new type. This is also the procedure for instance-store-rooted instances where stop-modify-start isn't available.
Step 1: Make sure ENA and NVMe modules are loaded inside the instance (Amazon Linux 2 / 2023, Ubuntu 18.04+, RHEL 7.4+ all ship them):
sudo modprobe ena
sudo modprobe nvme
lsmod | grep -E "ena|nvme"Step 2: Create an AMI from the running instance. EC2 quiesces I/O briefly to capture a consistent snapshot:
aws ec2 create-image \
--region :region \
--instance-id :instance_id \
--name "migration-$(date +%Y%m%d-%H%M)" \
--description "Pre-Nitro migration" \
--no-reboot--no-reboot skips the OS-level fsync that a clean shutdown gives you. For database-heavy instances drop the flag (or run sync manually first) so the snapshot is filesystem-consistent.
Step 3: Enable ENA on the new AMI explicitly. EC2 honors the source instance's ENA attribute, but for older AMIs the attribute may not propagate:
aws ec2 modify-image-attribute \
--region :region \
--image-id ami-0newamiid \
--ena-supportStep 4: Launch a fresh instance from the new AMI on the destination type:
aws ec2 run-instances \
--region :region \
--image-id ami-0newamiid \
--instance-type :new_type \
--subnet-id subnet-0abc \
--security-group-ids sg-0abc \
--key-name my-key \
--iam-instance-profile Name=my-instance-profileStep 5: Reattach Elastic IPs, update DNS / load balancer targets, terminate the old instance after verification.
This is the "no-shortcut" path. It costs an AMI creation (a few cents and 5 to 15 minutes), plus the overlap during which both instances run. Worth it for anything where a failed start-instances would cause customer-visible downtime.
Method 4: ASG rolling replace (zero downtime in production)
For production fleets, the right pattern is to push the new instance type into the Launch Template and let the Auto Scaling Group roll instances one at a time. Zero downtime, full health-check gating, automatic rollback if the new type fails its target-group health check.
Update the launch template with the new instance type:
aws ec2 create-launch-template-version \
--region :region \
--launch-template-id lt-0abc \
--source-version 1 \
--launch-template-data '{"InstanceType":":new_type"}'Point the ASG at the new version:
aws autoscaling update-auto-scaling-group \
--region :region \
--auto-scaling-group-name web-prod \
--launch-template "LaunchTemplateId=lt-0abc,Version=2"Trigger the rolling replace. The instance-refresh API replaces instances in batches while respecting min-healthy-percentage:
aws autoscaling start-instance-refresh \
--region :region \
--auto-scaling-group-name web-prod \
--preferences '{"MinHealthyPercentage":90,"InstanceWarmup":120}'MinHealthyPercentage: 90 means the ASG keeps at least 90% of the fleet in service during the rollover. InstanceWarmup is how long a new instance has to pass health checks before the next batch starts. The ASG launches a new instance on the new type, waits for the target group to mark it healthy, then drains the old instance and terminates it. Repeat until the whole fleet runs on the new type.
If the new type fails health checks (network missing because ENA isn't loaded, NVMe driver missing) the refresh halts automatically and the existing instances stay in service. Roll back by updating the launch template back to the previous version and starting another refresh.
This is the only pattern I run on customer-facing production. For internal tools and batch workers, Method 1 is fine.
Cost angle: vertical scale vs gp3 + smaller box
Before vertically scaling an instance because it's running out of capacity, check what's actually constrained. Three common findings turn a "bigger instance" decision into a cheaper "different EBS" decision:
- Disk IOPS is the bottleneck, not CPU. A
t3.mediumon agp2volume of less than 1 TB is throttled to ~3 IOPS per GB with a 3,000 IOPS burst. Switching the volume togp3gives you 3,000 IOPS baseline + provisioned IOPS up to 16,000 at lower cost than the equivalentgp2upgrade. Often eliminates the need for a bigger instance. - EBS throughput is capped by instance type, not volume. A
t3.largecaps at 174 MB/s of aggregate EBS throughput regardless of how big the volume is. Am5.largeallows 593 MB/s on the same volume. Sometimes the right answer is a slightly bigger instance with the same disk, not a bigger disk on the same instance. - Memory pressure looks like CPU pressure. A small instance under memory pressure starts swapping (or OOM-killing); the visible symptom is CPU saturation. Run
free -hand check swap usage before assuming you need more vCPU.
The general order: profile first, switch gp2 to gp3 second, vertically scale third.
Stop vs Hibernate vs Terminate
Three operations look similar in the console; only one is right for changing an instance type.
| Operation | What it does | Use for resize? |
|---|---|---|
| Stop | Powers off, releases the underlying host, EBS volumes persist, instance-store volumes wipe | Yes |
| Hibernate | Saves RAM contents to the root EBS volume, then stops | No: incompatible with modify-instance-attribute for type changes |
| Terminate | Powers off and deletes the instance + (by default) the root EBS volume | No: this is destructive |
Stop is the correct verb for a resize. Hibernate preserves RAM state, which is useful for warm-restart workflows but conflicts with the instance-type change because the saved RAM image is tied to the original instance's CPU and memory shape. Terminate is for "I'm done with this instance forever," not "I want to make it bigger."
Comparison: four ways to change an instance type
| Approach | Downtime | Risk | When to use |
|---|---|---|---|
| Stop + modify + start | 30 to 90 seconds | Low (one instance) | Single dev / staging / batch instance |
| Snapshot AMI + relaunch | None on the original, then DNS cutover | Lower (old instance stays up) | Xen → Nitro jump, instance-store root, untested AMI |
| ASG rolling replace | None | Lowest (gated by health checks) | Production fleets behind a load balancer |
Terraform instance_type change | 30 to 90 seconds (default) | Low if lifecycle configured | Infra-as-code estates; pair with ASG for prod |
Terraform note: changing instance_type on an aws_instance resource performs an in-place modification (stop, modify, start), not a replacement. The instance ID stays the same. Confirm by running terraform plan first; the line should read ~ instance_type (modify) not -/+ (replace). If it reads replace, an unrelated attribute is forcing a new resource; look at the diff above to find which one.
Common pitfalls
1. Instance store data loss on stop. Anything on /dev/nvme1n1 or higher (the instance-store volumes on m5d, r5d, c5d, i3, x1, x2) is wiped the moment you stop the instance. EBS root survives. Instance store does not. Back up first.
2. Public IPv4 changes after stop. Unless you have an Elastic IP, the public IPv4 is released on stop and reassigned (from a different pool) on start. DNS records pointing at the old IP break. Either use an Elastic IP for anything where the IP matters, or update DNS as part of the resize procedure.
3. ENA / NVMe missing on the AMI. The instance boots, fails its 2/2 status check, and you spend 20 minutes wondering why. Either install the drivers in the AMI before the resize, or use Method 3 and bake a new AMI explicitly.
4. Burst credits reset on stop for T-family. T3, T3a, T4g instances accumulate CPU credits while running and burn them during spikes. Stop resets the accumulated credit balance to zero (Unlimited mode billing continues). Right after a T-family resize, expect cooler baseline performance for a few hours while credits rebuild.
5. Reserved Instance and Savings Plan coverage. If the instance was covered by a Reserved Instance for the old type, the RI does not automatically cover the new type. Convertible RIs can be exchanged; Standard RIs cannot. Savings Plans (Compute Savings Plans specifically) cover any instance type in the family region, so they're more forgiving for resize-heavy fleets.
6. Dedicated tenancy and dedicated hosts. A shared-tenancy instance cannot be modified into a dedicated-tenancy instance and vice versa. The error message reads "InvalidInstanceAttributeValue" with no further explanation; check tenancy first.
7. Placement group families. Cluster placement groups require 10 Gbps capable instance types. Trying to modify into a type that doesn't qualify (older T family, for example) fails the start. Spread placement groups have laxer rules. Partition placement groups have the same general constraint as cluster.
8. --instance-type JSON quoting on Windows / PowerShell. The '{"Value": "t3.large"}' JSON needs different quoting in PowerShell. Use --instance-type "{\"Value\": \"t3.large\"}" or the shorthand --instance-type t3.large if your CLI version supports it.
What to do next
- SSH into an EC2 instance for verifying the resize from inside the box.
- Add an EBS volume to an EC2 instance when "bigger disk" turns out to be a better fix than "bigger instance."
- Extend an EBS volume without restart for growing an existing volume in place.
- AWS IAM policy examples for the EC2 admin and pass-role policies the resize procedure needs.
- AWS S3 cp and sync cheat sheet for syncing instance-store data to S3 before the stop.
- Cross-cloud: Increase a Google Cloud VM disk size without rebooting, GCP add persistent disk to VM, SSH into a GCP VM without gcloud.
- Scripting: Bash for loop and Bash while loop for batch-resizing a fleet of instances or polling the
wait instance-runningstate. - SSH cheat sheet for the connection-survival flags worth setting during a stop / start cycle.
- External: AWS EC2 instance type changes (UserGuide), Compatibility for changing the instance type, aws ec2 modify-instance-attribute reference.





