TechEarl

How to Extend an AWS EBS Volume Without a Restart

Grow an EBS volume on a running EC2 instance in four steps. Modify the volume, wait for the optimizing state, expand the partition with growpart, then stretch the filesystem with resize2fs or xfs_growfs. No detach, no reboot.

Ishan KarunaratneIshan Karunaratne⏱️ 18 min readUpdated
Grow an AWS EBS volume with zero downtime: aws ec2 modify-volume to enlarge, wait for the optimizing state, then sudo growpart to extend the partition and sudo resize2fs (ext4) or sudo xfs_growfs (XFS) to stretch the filesystem. No detach, no reboot, on a live EC2 instance.

AWS EBS volumes grow online. The Elastic Volumes feature (GA February 2017) added the control-plane support; modern Nitro instances expose the new size to Linux within seconds. The whole operation is four commands once you know the device path: aws ec2 modify-volume to enlarge the volume at the EBS layer, aws ec2 describe-volumes-modifications to confirm the modification reached the optimizing state, sudo growpart to expand the partition, and sudo resize2fs (ext4) or sudo xfs_growfs (XFS) to stretch the filesystem. No detach, no reboot, no maintenance window. Below is the exact procedure, the verification commands to run before and after, the two caveats that bite people once (the 6-hour modification cooldown and the AWS-vs-Linux device naming mismatch), and a Console vs CLI vs Terraform comparison.

How do I extend an AWS EBS volume without rebooting?

Run aws ec2 modify-volume --volume-id vol-XXX --size 200 to enlarge the volume at the EBS layer. The control plane acknowledges the request immediately; the volume moves through modifying to optimizing to completed over the next few minutes. The new size is visible from inside the instance the moment the volume reaches optimizing, which usually happens within seconds. SSH into the instance and confirm the kernel sees it with lsblk. Expand the partition with sudo growpart /dev/nvme1n1 1 (note the space between the device and the partition number). Finally, stretch the filesystem: sudo resize2fs /dev/nvme1n1p1 for ext4 or sudo xfs_growfs /mountpoint for XFS. Verify with df -h. The entire flow runs against a live EC2 instance with active connections, services, and I/O. The only constraint is one modification per six hours on the same volume.

Try it with your own values

Jump to:

Before you start: prerequisites and snapshot

You need an IAM principal with ec2:ModifyVolume and ec2:DescribeVolumesModifications. The instance can be running. The volume must be attached.

Elastic Volumes (the feature that makes online modification possible) has been GA since February 2017 and works on every modern instance type. The one prerequisite caveat is the cutoff date: volumes attached to instances before November 3, 2016 23:40 UTC need to be "initialized" by either detaching and reattaching the volume or stopping and starting the instance. For anything launched in the last several years this is not a concern.

Take a snapshot first. Snapshots are incremental, cost cents, and are the only single-API-call rollback if the partition table or filesystem goes sideways:

bash
aws ec2 create-snapshot \
  --region :region \
  --volume-id :volume_id \
  --description "pre-resize-$(date +%Y%m%d-%H%M)"

A 100 GiB volume snapshot completes in seconds for incremental snapshots after the first. Mistakes on a live disk are not cheap.

Capture the current state so you have a before-and-after to compare:

bash
df -hT
lsblk

df -hT shows mounted filesystems with their type and used/free space. lsblk shows the block-device tree (disk to partitions to filesystems) and is where you confirm the new size lands.

Step 1: Modify the volume

The AWS CLI one-liner:

bash
aws ec2 modify-volume \
  --region :region \
  --volume-id :volume_id \
  --size :new_size

--size is in GiB. The response shows the modification state:

json
{
    "VolumeModification": {
        "VolumeId": "vol-0abc123def456",
        "ModificationState": "modifying",
        "TargetSize": 200,
        "OriginalSize": 100,
        "Progress": 0,
        "StartTime": "2026-04-10T13:22:01.000Z"
    }
}

The same command also changes volume type, IOPS, or throughput. For example, converting a gp2 volume to gp3 with provisioned performance:

bash
aws ec2 modify-volume \
  --region :region \
  --volume-id :volume_id \
  --volume-type gp3 \
  --iops 6000 \
  --throughput 250 \
  --size :new_size

All four attributes can change in one call. The control plane handles each independently.

Console method: EC2 console, Volumes, select the volume, Actions, Modify volume, change Size, Modify. Same effect, slower for batches.

Step 2: Wait for the optimizing state

The modification moves through three states: modifying, optimizing, completed.

  • modifying is the early phase where AWS is provisioning the resize at the EBS backend. New size is not yet visible to the instance.
  • optimizing is when the resize is functionally complete. The new size is visible to the instance from inside the OS. Performance characteristics may still be settling (especially for volume type changes), but the size operation itself is done.
  • completed is fully settled. Volume reports its target configuration on every metric.

The new size is visible at optimizing, not at completed. Waiting for completed is unnecessary if all you care about is the size.

Poll the modification state:

bash
aws ec2 describe-volumes-modifications \
  --region :region \
  --volume-ids :volume_id \
  --query "VolumesModifications[0].ModificationState" \
  --output text

Or wait in a Bash while loop until it reaches optimizing:

bash
while true; do
  state=$(aws ec2 describe-volumes-modifications \
      --region :region \
      --volume-ids :volume_id \
      --query "VolumesModifications[0].ModificationState" \
      --output text)
  [[ "$state" == "optimizing" || "$state" == "completed" ]] && break
  sleep 5
done

For gp3 size-only modifications the wait is usually under a minute. For volume type conversions (gp2 to gp3, or anything to io2 Block Express) the optimizing phase can take much longer as data is rebalanced across the EBS backend, but the size is usable as soon as the state transitions.

From inside the instance, confirm the kernel sees the new geometry:

bash
lsblk

The disk size should match the new value. If it does not, the volume is still in modifying (wait a moment) or, very rarely, the kernel needs a nudge to rescan. On modern Nitro instances this rescan happens automatically. On older Xen instances you may need:

bash
echo 1 | sudo tee /sys/class/block/xvdf/device/rescan

This is rare in practice; the auto-detect path is reliable on every current AMI.

Step 3: Expand the partition

The kernel now sees the larger disk, but the partition table still says "this partition ends at the old boundary." Fix that with growpart from the cloud-utils-growpart package (pre-installed on every official AWS AMI).

For a Nitro instance with a partitioned data volume:

bash
sudo growpart :device :partition

Two arguments, space-separated: the device (/dev/nvme1n1) and the partition number (1). The space matters. A common typo is growpart /dev/nvme1n1p1 (no space, partition path concatenated) which fails because growpart needs the disk and the partition index separately.

For an older Xen instance:

bash
sudo growpart /dev/xvdf 1

For the root volume on a Nitro instance (this is the common case for boot disks):

bash
sudo growpart /dev/nvme0n1 1

Output looks like:

code
CHANGED: partition=1 start=2048 old: size=209713119 end=209715167 new: size=419430399 end=419432447

If growpart is not installed (rare, but happens on minimal or custom images):

bash
# Amazon Linux / RHEL / Rocky / CentOS
sudo dnf install -y cloud-utils-growpart

# Debian / Ubuntu
sudo apt-get update && sudo apt-get install -y cloud-guest-utils

For volumes formatted as a whole-disk filesystem (no partition table, common on additional data volumes formatted with mkfs.ext4 /dev/nvme1n1 directly), skip this step and go straight to the filesystem resize against the raw device. lsblk tells you which model your disk is on: if the disk appears with no partitions underneath it, you have a whole-disk filesystem.

Step 4: Resize the filesystem

The partition is now the full size of the volume, but the filesystem inside the partition still believes it is the old size. The command depends on the filesystem type.

ext4 (Amazon Linux 2, Ubuntu, Debian, most legacy custom AMIs):

bash
sudo resize2fs /dev/nvme1n1p1

For a whole-disk ext4 (no partition):

bash
sudo resize2fs :device

resize2fs reads the device geometry and grows the ext4 superblock to match. Online resize on a mounted filesystem is supported and is what runs here. Expected output:

code
resize2fs 1.45.5 (07-Jan-2020)
Filesystem at /dev/nvme1n1p1 is mounted on /; on-line resizing required
old_desc_blocks = 13, new_desc_blocks = 25
The filesystem on /dev/nvme1n1p1 is now 52428795 (4k) blocks long.

XFS (Amazon Linux 2023 root volumes, RHEL 8+, Rocky, AlmaLinux):

bash
sudo xfs_growfs /

For a data volume mounted at /mnt/data:

bash
sudo xfs_growfs /mnt/data

XFS resizes by mount point, not by device. XFS can only grow, never shrink, which matches the EBS modification constraint anyway. The -d flag (sudo xfs_growfs -d /) explicitly grows to fill the device; current xfs_growfs does that by default but the flag is harmless and shows up in older AWS documentation.

btrfs (uncommon on AWS, but supported):

bash
sudo btrfs filesystem resize max /

If you do not know the filesystem type, the next section has the detection commands.

Verify the new size

bash
df -hT
lsblk

df -hT should now show the new free space on the mount point. lsblk should show the partition matching the volume size.

A complete before-and-after on a Nitro instance's data volume grown from 100 GiB to 200 GiB:

code
# Before
$ df -hT /mnt/data
Filesystem      Type   Size  Used Avail Use% Mounted on
/dev/nvme1n1p1  ext4    98G   42G   52G  45% /mnt/data

# After
$ df -hT /mnt/data
Filesystem      Type   Size  Used Avail Use% Mounted on
/dev/nvme1n1p1  ext4   196G   42G  149G  22% /mnt/data

Same Used, larger Size. That is the green light. Total wall-clock for the four commands on a typical data volume: under a minute, no reboot, active SSH session never drops.

Detect the filesystem before you resize it

Run any of these to identify the filesystem type and partition layout. Useful when you inherited an instance and do not know what shape its disk is in.

bash
# Filesystem type per partition
lsblk -f

# Filesystem type and mount info for the root mount
df -hT /

# Disk and partition geometry
sudo parted -l

# Quick filesystem type lookup by device
sudo blkid /dev/nvme1n1p1

lsblk -f is the fastest single command and shows everything: device tree, filesystem type, label, UUID, and mountpoint.

Cheat sheet for picking the resize command:

FilesystemResize command
ext2 / ext3 / ext4sudo resize2fs /dev/DEVICE
XFSsudo xfs_growfs MOUNT_POINT
btrfssudo btrfs filesystem resize max MOUNT_POINT
ZFSsudo zpool online -e POOL DEVICE
FAT32 / NTFSnot online-resizable on Linux

The first two cover ~99% of EC2 Linux instances.

Console vs AWS CLI vs Terraform

The resize itself is identical at the API layer. The interface choice is about your workflow.

ApproachBest forCommand / action
EC2 ConsoleOne-off resizes, exploringVolumes, Actions, Modify volume
AWS CLIScripted, one-shot, ad-hoc batchesaws ec2 modify-volume --volume-id vol-XXX --size N
TerraformInfra-as-code, audit trail, multi-environmentaws_ebs_volume.size = 200 then terraform apply
AWS CDKProgrammatic, app-alignedvolume.size = cdk.Size.gibibytes(200)
REST APIProgrammatic, custom toolingPOST /?Action=ModifyVolume against the EC2 endpoint

For Terraform, the attribute that changes is size (plus optionally iops, throughput, or type):

hcl
resource "aws_ebs_volume" "data" {
  availability_zone = "us-east-1a"
  size              = 200  # was 100
  type              = "gp3"
  iops              = 3000
  throughput        = 125
  encrypted         = true
  tags = {
    Name = "data-disk-01"
  }
}

Terraform performs the resize in place. It does NOT trigger a replacement, which is exactly what you want. The growpart and resize2fs steps still have to happen inside the instance, typically via a remote-exec provisioner, a separate Ansible run, or an AWS Systems Manager Run Command document.

The CDK equivalent in TypeScript:

typescript
const dataVolume = new ec2.Volume(this, "DataVolume", {
    availabilityZone: "us-east-1a",
    size: cdk.Size.gibibytes(200), // was 100
    volumeType: ec2.EbsDeviceVolumeType.GP3,
    encrypted: true,
});

To batch-resize many volumes via the CLI, a Bash for loop over a volume-ID list is the simplest pattern:

bash
for vol in vol-aaa vol-bbb vol-ccc; do
  aws ec2 modify-volume --region :region --volume-id "$vol" --size :new_size
done

Pair with a Bash while loop and aws ec2 describe-volumes-modifications if you need to wait for each volume to reach optimizing before triggering the next instance-side step.

Caveats and gotchas

You cannot shrink an EBS volume. Ever. The only path to a smaller volume is: create a new smaller volume, copy data over, swap the attachment, delete the old one. Plan capacity with this in mind; over-provisioning is reversible only the hard way.

One modification per six hours per volume. This is the most-cited Elastic Volumes constraint and the one that bites teams who plan resizes without reading the limits. The 6-hour cooldown applies to the same volume; modifications to other volumes are independent. If you need to retry a failed modification or follow up with a second one, the cooldown clock starts at the moment the first modification reached modifying.

The new size is visible at the optimizing state, not completed. A common mistake is waiting for completed before running growpart. optimizing is sufficient and usually arrives within seconds for size-only changes. For volume type conversions (gp2 to gp3, or anything to io2 Block Express) optimizing can take hours, but the size is already usable.

Nitro vs Xen device naming. On Nitro instances (C5 and later, every Graviton, T3, T3a, M5, R5, and modern types) EBS volumes appear as /dev/nvmeXn1. On older Xen instances they appear as /dev/xvdX. The growpart command differs accordingly. See How to Add an EBS Volume to an EC2 Instance for the full mapping table.

Whole-disk filesystems skip the growpart step. If lsblk shows your volume as a single disk with no partition rows underneath (no nvme1n1p1, just nvme1n1), the filesystem was created on the raw device. Run resize2fs /dev/nvme1n1 or xfs_growfs /mnt/data directly; growpart is not needed and will fail with "must supply partition-number".

io2 Block Express has a different optimization path. Conversions between standard io2 and io2 Block Express (the high-IOPS variant) involve more substantial backend work. The size change is fast but the throughput rebalancing during optimizing can run for hours. Plan accordingly.

Custom AMIs may not auto-detect the new size at boot. Official AWS AMIs (Amazon Linux 2, Amazon Linux 2023, Ubuntu) ship cloud-init configured to grow the root partition and filesystem on first boot. Custom AMIs built from a non-cloud-init base do not. If you are on a custom image, the four-step procedure above is mandatory and an actual reboot will not save you.

Snapshots before, not regrets after. A snapshot is faster than typing this sentence and saves the volume in its current state. Cost is incremental-only, measured in cents. There is no excuse to skip it on production volumes.

Performance scales with size on some types. gp2 IOPS scales at 3 IOPS per GiB. Going from 100 GiB to 200 GiB doubles your IOPS ceiling. gp3 decouples IOPS from size, so a resize alone does not change performance there; modify --iops and --throughput explicitly. io1 and io2 are pure provisioned IOPS and unaffected by size in performance terms.

Don't run fdisk just because an old guide tells you to. Some older blog posts advise running sudo fdisk /dev/nvme0n1 and pressing w to "fix the GPT partition table" before growpart. This is unnecessary on modern AWS AMIs and risks corruption if you press the wrong key in fdisk's interactive mode. growpart handles the partition table update on its own.

What to do next

FAQ

TagsAWSEC2EBSElastic VolumesLinuxgrowpartresize2fsxfs_growfsNitro
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years across software, Linux systems, DevOps, and infrastructure — and a more recent focus on AI. Currently Chief Technology Officer at a tech startup in the healthcare space.

Keep reading

Related posts

Create an EBS volume with aws ec2 create-volume, attach it to a running EC2 instance, format with mkfs.ext4 or mkfs.xfs, mount it, and persist across reboots with a UUID-based /etc/fstab entry. Console, AWS CLI, and Terraform walkthroughs.

How to Add an EBS Volume to an EC2 Instance

Create an EBS volume, attach it to a running EC2 instance, format and mount it, and survive reboots with a UUID-based fstab entry. Console, AWS CLI, and Terraform walkthroughs plus the Nitro device-naming gotcha that trips everyone.

Change an EC2 instance type without data loss: stop the instance, run aws ec2 modify-instance-attribute, start it again. Covers Nitro vs Xen compatibility, ENA and NVMe driver requirements, the instance-store ephemeral-data trap, and the zero-downtime ASG rolling-replace pattern.

How to Change an AWS EC2 Instance Type (Resize Without Data Loss)

Stop the instance, modify the instance type, start it. The exact gcloud-equivalent AWS CLI syntax, the compatibility matrix for moving between families and generations, the Nitro vs Xen gotcha, the instance-store data-loss trap, and the production sequence (snapshot AMI, scale out, replace) that gets you to a new instance type with zero downtime.

Macro photograph of a printer's typecase drawer with brass-and-wood compartments, one new compartment freshly added at the end, single warm side light

How to Add a Column to a MySQL Table

Add a column to a MySQL table with ALTER TABLE ADD COLUMN. Covers DEFAULT values, NOT NULL on existing rows, AFTER positioning, and ALGORITHM=INSTANT on MySQL 8.0.12+.