You moved to a bigger plan, or you attached a larger disk, and df -h
still shows the old size. The disk did grow. What sits on top of it did not: the
partition, sometimes an LVM layer, then the filesystem. Each layer has to be
extended in that order, and each one has its own command.
Everything below was run on a Debian 13 test machine with three disks that we enlarged while it was running: a root partition in ext4, a data disk in XFS and a data disk under LVM. The outputs are the real ones, and none of it needs a reboot.
1. Often, a reboot is enough
Most of our images grow their root partition on their
own: cloud-init runs growpart and resizes the filesystem every time the
machine starts. Oracle Linux is the exception, because its root sits on LVM and
cloud-init leaves LVM alone. Our Oracle Linux images add a small service that runs
the whole chain at every boot. On a Starter (40 GB), the
root comes up at 34.8 GB. Move the same machine to an 80 GB disk, reboot, and it
reaches 74.8 GB.
If you can afford a reboot, try that first. The rest of this article is for when you can't, or when the space belongs to a second disk or to an LVM layout you set up yourself.
2. Find where the space is stuck
lsblk shows every layer with its size. Right after the three disks
grew, each disk is larger than the partition it holds:
$ lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS /dev/sda /dev/sdb /dev/sdc
NAME SIZE TYPE FSTYPE MOUNTPOINTS
sda 12G disk
├─sda1 5.9G part ext4 /
├─sda14 3M part
└─sda15 124M part vfat /boot/efi
sdb 6G disk
└─sdb1 2G part xfs /srv/data
sdc 6G disk
└─sdc1 2G part LVM2_member
└─vg_data-lv_srv 2G lvm xfs /srv/lvm
df -h confirms that the filesystems have not moved:
$ df -h / /srv/data /srv/lvm
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 5.8G 915M 4.6G 17% /
/dev/sdb1 2.0G 71M 1.9G 4% /srv/data
/dev/mapper/vg_data-lv_srv 2.0G 71M 1.9G 4% /srv/lvm
Then ask findmnt what holds the mount point you want to grow, and in
which format. The answer tells you which case you are in:
findmnt -n -o SOURCE,FSTYPE /point | Case | Commands |
|---|---|---|
/dev/sda1 ext4 | plain partition, ext4 | growpart, resize2fs |
/dev/sdb1 xfs | plain partition, XFS | growpart, xfs_growfs |
/dev/mapper/… | LVM | growpart, pvresize, lvextend -r |
3. Install growpart
growpart rewrites the partition table so that a partition takes all the
free space that follows it. It is usually already there on cloud images, since
cloud-init relies on it. Otherwise, the package name depends on the family:
| Distribution | Package |
|---|---|
| Debian, Ubuntu, Arch Linux | cloud-guest-utils |
| AlmaLinux, Rocky Linux, CentOS Stream, Oracle Linux, Fedora | cloud-utils-growpart |
| openSUSE | growpart |
| Alpine | cloud-utils-growpart |
The filesystem tools come from e2fsprogs (resize2fs),
xfsprogs (xfs_growfs) and lvm2.
4. Case 1: a plain partition in ext4
Before touching anything, fdisk -l may greet you with this:
GPT PMBR size mismatch (12582911 != 25165823) will be corrected by write.
The backup GPT table is not on the end of the device.
GPT keeps a backup copy of the partition table in the last sectors of the disk. When
the disk grows, that copy is no longer at the end. It is harmless, and
growpart moves the copy when it rewrites the table: after the next step,
fdisk no longer complains.
The disk and the partition number are two separate arguments. Start with
--dry-run, which shows the change without writing it:
$ sudo growpart --dry-run /dev/sda 1
CHANGE: partition=1 start=262144 old: size=12320735 end=12582878 new: size=24903647 end=25165790
$ sudo growpart /dev/sda 1
CHANGED: partition=1 start=262144 old: size=12320735 end=12582878 new: size=24903647 end=25165790
The partition now covers the disk, but the filesystem still has its old size.
resize2fs grows a mounted ext4 in place:
$ sudo resize2fs /dev/sda1
resize2fs 1.47.2 (1-Jan-2025)
Filesystem at /dev/sda1 is mounted on /; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 2
The filesystem on /dev/sda1 is now 3112955 (4k) blocks long.
$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 12G 915M 11G 9% /
Running growpart again does no harm. It reports that there is nothing
left to take:
$ sudo growpart /dev/sda 1
NOCHANGE: partition 1 is size 24903647. it cannot be grown
growpart can only extend a partition into the free space right after it.
On this Debian image, partition 1 is the last one on the disk (partitions 14 and 15
sit at the start), so it has room. If another partition sits behind yours, you also
get NOCHANGE, and that partition has to be moved or removed first.
5. Case 2: XFS
XFS is the default filesystem of the Red Hat family. The first step is the same.
The second differs: xfs_growfs takes the mount point, not the device,
and only works on a mounted filesystem.
$ sudo growpart /dev/sdb 1
CHANGED: partition=1 start=2048 old: size=4190208 end=4192255 new: size=12580831 end=12582878
$ sudo xfs_growfs /srv/data
[…]
data blocks changed from 523776 to 1572603
$ df -h /srv/data
Filesystem Size Used Avail Use% Mounted on
/dev/sdb1 6.0G 150M 5.8G 3% /srv/data
xfs_growfs prints the whole filesystem geometry before that last line;
we cut it here.
6. Case 3: LVM
LVM adds a layer between the partition and the filesystem. The partition is a
physical volume (PV), the PVs form a volume group (VG), and the VG is cut into
logical volumes (LV). After growpart, the PV has to be told about its
new size, which gives the group free space:
$ sudo vgs
VG #PV #LV #SN Attr VSize VFree
vg_data 1 1 0 wz--n- <2.00g 0
$ sudo growpart /dev/sdc 1
CHANGED: partition=1 start=2048 old: size=4190208 end=4192255 new: size=12580831 end=12582878
$ sudo pvresize /dev/sdc1
Physical volume "/dev/sdc1" changed
1 physical volume(s) resized or updated / 0 physical volume(s) not resized
$ sudo vgs
VG #PV #LV #SN Attr VSize VFree
vg_data 1 1 0 wz--n- <6.00g 4.00g
lvextend -r hands that free space to the logical volume and grows the
filesystem in the same command, whether it is ext4 or XFS:
$ sudo lvextend -r -l +100%FREE /dev/vg_data/lv_srv
File system xfs found on vg_data/lv_srv mounted at /srv/lvm.
Size of logical volume vg_data/lv_srv changed from <2.00 GiB (511 extents) to <6.00 GiB (1535 extents).
Extending file system xfs to <6.00 GiB (6438256640 bytes) on vg_data/lv_srv...
[…]
Extended file system xfs on vg_data/lv_srv.
Logical volume vg_data/lv_srv successfully resized.
$ df -h /srv/lvm
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/vg_data-lv_srv 6.0G 150M 5.8G 3% /srv/lvm
A root on LVM follows the same path. findmnt / gives the logical volume
and pvs gives the partition under it. On our Oracle Linux images, that
is partition 4:
sudo growpart /dev/sda 4
sudo pvresize /dev/sda4
sudo lvextend -r -l +100%FREE /dev/mapper/vg_main-lv_root
7. The other direction
Growing works live. Shrinking does not. XFS cannot be shrunk: recent versions of
xfs_growfs carry an experimental attempt, and on our test machine it
failed:
$ sudo xfs_growfs -D 1000000 /srv/data
[…]
[EXPERIMENTAL] try to shrink unused space 1000000, old size is 1572603
xfs_growfs: XFS_IOC_FSGROWFSDATA xfsctl failed: Invalid argument
ext4 can shrink, but only when it is not mounted, which rules out a running root:
$ sudo resize2fs /dev/sda1 8G
resize2fs 1.47.2 (1-Jan-2025)
Filesystem at /dev/sda1 is mounted on /; on-line resizing required
resize2fs: On-line shrinking not supported
To end up with a smaller filesystem, you create a new one at the size you want and copy the data across.
8. Before you type
These commands rewrite a partition table on a running system. They are routine, but
a wrong device name hits the wrong disk. Take a backup from the console first: every
plan includes 7 manual backups. Read the old and new sizes that
growpart --dry-run prints before you drop the option. And check the
device name twice, since sda and sdb differ by one letter.