Before you read. A database partition is at 96% and filling. You have a spare disk in the machine.
With plain partitions, the options are: shrink the neighbouring partition and grow this one, which means unmounting and hoping; or mount the new disk somewhere else and change the application to use two paths; or take an outage, move everything, and repartition.
None of those is good, and all of them exist for one reason: a partition’s start and end are decided when it is created, and everything after it on the disk is in the way.
So the question: what would have to change for “make this filesystem bigger” to be a thing you could do on a Tuesday afternoon, with the database running?
The answer is a layer of indirection. LVM sits between the disks and the filesystems and stops them being adjacent, so growing one no longer means moving another.
This is a deep-dive topic, and it is on the exam because the RHEL family installs onto LVM by default. There is a reasonable chance the machine in front of you is already using it.
Some words you will need
- physical volume (PV)
- A disk or partition handed over to LVM. It stops being used directly.
- volume group (VG)
- A pool made of one or more physical volumes. Capacity goes in here.
- logical volume (LV)
- A slice taken out of the pool. Behaves exactly like a partition, and this is what you put a filesystem on.
- extent
- The unit LVM allocates in, 4 MiB by default. Sizes are always a whole number of these.
What breaks without this
Growing storage becomes an outage. Every capacity increase means a maintenance window, a backup, and a repartition, instead of two commands.
You cannot read the machine in front of you. On a RHEL-family default install,
/dev/sda2 is a physical volume and the root filesystem is on
/dev/mapper/rhel-root. Somebody who only knows partitions cannot follow that,
and df output that names /dev/mapper/ anything is telling you LVM is in play.
You do the resize half way. Growing the volume and not the filesystem is the single most common LVM mistake, and it produces no error at all.
The three layers
The whole trick is in the middle box. Once capacity is a pool, a logical volume is not sitting between two neighbours. Growing it means taking more extents from the pool, and the extents do not have to be next to the ones it already has, or even on the same disk.
Building it
Three commands, bottom up. The two devices below are raw disks that have never been partitioned.
Every guide to adding a disk starts by making a partition table. Does pvcreate require one, and what does the VG column say for a brand new physical volume?
# AlmaLinux 10.2, aarch64
$ pvcreate $DEVS; echo; pvs
Physical volume "/dev/loop0" successfully created.
Physical volume "/dev/loop1" successfully created.
Creating devices file /etc/lvm/devices/system.devices
PV VG Fmt Attr PSize PFree
/dev/loop0 lvm2 --- 512.00m 512.00m
/dev/loop1 lvm2 --- 512.00m 512.00m
pvcreate writes an LVM header onto each device. Two 512 MiB disks, both entirely
free, and the VG column is empty because neither belongs to a group yet.
Note there are no partitions here. LVM is happy to take a whole disk, and on a data disk that is the tidier choice, one fewer layer, and no partition table to keep in step.
Then the group, and a volume out of it:
# AlmaLinux 10.2, aarch64
$ pvcreate -q $DEVS 2>/dev/null; vgcreate data $DEVS; echo; vgs; echo; lvcreate -n web -L 300M data; echo; lvs; echo; lsblk $DEVS
Physical volume "/dev/loop0" successfully created.
Physical volume "/dev/loop1" successfully created.
Creating devices file /etc/lvm/devices/system.devices
Volume group "data" successfully created
VG #PV #LV #SN Attr VSize VFree
data 2 0 0 wz--n- 1016.00m 1016.00m
Logical volume "web" created.
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
web data -wi-a----- 300.00m
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 512M 0 loop
`-data-web 252:0 0 300M 0 lvm
loop1 7:1 0 512M 0 loop
Three things to read out of that.
VSize 1016.00m from two 512 MiB disks. Not 1024. Each PV gives up a little
to LVM’s own metadata, so the pool is always slightly smaller than the sum of the
parts. Expect it and do not go looking for the missing megabytes.
The lsblk tree. data-web is nested under loop0, and loop1 has nothing
under it. The volume fitted entirely on the first disk, because 300 MiB fits in
512 MiB. LVM allocates linearly by default and only spills onto the second disk
when it has to.
The volume is at /dev/data/web, and also at /dev/mapper/data-web. Both
work. /dev/mapper/<vg>-<lv> is the one that shows up in df output, which
is why a df line naming /dev/mapper/ anything means you are looking at
LVM.
The three reporting commands go together and are worth learning as a set:
| Command | Answers |
|---|---|
pvs |
Which devices are in LVM, and how much of each is used |
vgs |
How big each pool is and how much is free |
lvs |
Which volumes exist and how big |
pvdisplay |
The same about one device at length, including its extent count |
vgdisplay |
The same about one group, including the extent size and free extents |
lvdisplay |
The same about one volume, including its path and whether it is active |
There are two families and both are named in the objectives. The short set prints
a line per object and is what you reach for when you want to see everything at
once. The display set prints a paragraph per object and is what you reach for
when the short form leaves out the field you need.
VFree in vgs is the number to look at before any resize. It is the answer to
“can I grow this right now”.
Two more commands are worth having before the next section. lvresize does the
job of lvextend and lvreduce together, with the sign on the argument
deciding: -L +500M grows, -L -500M shrinks, and a bare -L 500M sets the
size outright. The separate commands still exist because shrinking a mounted
filesystem by accident is expensive, and having to type lvreduce is a small
speed bump in front of it.
The option worth remembering on any of the three is -r, which resizes the
filesystem in the same breath as the volume. It is the answer to the pitfall the
whole next section is about, and the reason to know the long way round first is
that -r only knows how to grow the filesystems it supports.
And lvchange is the one you need when a volume exists and is not there.
lvchange -ay data/web activates a volume, which is what an imported volume
group needs before anything appears under /dev/mapper. A volume that lvs
lists and ls /dev/mapper does not is almost always inactive rather than broken.
From here it is an ordinary block device: mkfs.ext4 /dev/data/web, mount it, put
it in /etc/fstab. The filesystem neither knows nor cares that LVM is underneath.
If you already administer Linux: extents, allocation, and why a volume can be slower than the disks under it
The extent is the unit of everything. 4 MiB by default, set at vgcreate
time with -s and awkward to change afterwards. Every logical volume is a whole
number of extents, which is why lvcreate -L 300M on a 4 MiB extent size gives
you exactly 300 MiB and -L 301M quietly gives you 304. vgdisplay shows the
extent size and the free count; lvs -o +seg_pe_ranges shows which extents on
which physical volume a given volume actually occupies.
That last command answers a question that comes up on any multi-disk group:
where is this volume, physically? LVM allocates linearly by default, filling
one PV before starting the next, so a volume can sit entirely on one spindle
while the group spans four. If that spindle is the slow one, the volume is slow
and nothing in lvs hints at it.
lvcreate -i 2 stripes across two physical volumes instead, which is a real
performance decision made at creation time and not easily changed later.
--alloc anywhere and --alloc contiguous are the other allocation policies,
and contiguous is occasionally worth it on spinning disks and never on SSDs.
pvmove is how you fix a bad placement without downtime. It relocates
extents off a physical volume (or, with pvmove /dev/sdb:1000-1999, a
specific range) while everything stays online. It is slow, restartable after
an interruption, and genuinely one of the more impressive things LVM does.
The resize, and the step everybody forgets
The volume is mounted and in use. Here is the whole operation:
lvextend -L +500M succeeds and reports the volume grew from 300 MiB to 800 MiB. What does df -h show immediately afterwards?
# AlmaLinux 10.2, aarch64
$ df -h /mnt/web | tail -1; echo "--- grow the logical volume ---"; lvextend -L +500M /dev/data/web; df -h /mnt/web | tail -1; echo "--- now grow the filesystem inside it ---"; resize2fs /dev/data/web; df -h /mnt/web | tail -1
/dev/mapper/data-web 272M 14K 253M 1% /mnt/web
--- grow the logical volume ---
Size of logical volume data/web changed from 300.00 MiB (75 extents) to 800.00 MiB (200 extents).
Logical volume data/web successfully resized.
/dev/mapper/data-web 272M 14K 253M 1% /mnt/web
--- now grow the filesystem inside it ---
resize2fs 1.47.1 (20-May-2024)
Filesystem at /dev/data/web is mounted on /mnt/web; on-line resizing required
old_desc_blocks = 3, new_desc_blocks = 7
The filesystem on /dev/data/web is now 819200 (1k) blocks long.
/dev/mapper/data-web 740M 14K 702M 1% /mnt/web
df shows exactly the same 272M. Not a rounding difference, the identical
line, before and after a command that reported complete success.
The reason is the layering from lesson 12. lvextend operates on the volume;
df reports the filesystem. Making the container bigger does not make its
contents bigger, and the filesystem is still using precisely the blocks it was
created with. There is now 500 MiB of space inside the volume that no filesystem
has claimed.
resize2fs is what tells the filesystem to expand into it, and only then does
df move, 272M to 740M.
Nothing warned you. lvextend did what it was asked and succeeded. There is no
error state here, just a job left half done, and the machine will keep filling up
while vgs cheerfully reports free space in the group. That is what makes this
the most common LVM mistake there is.
So the operation is always two steps, and the second one depends on the filesystem:
| Filesystem | Grow command | Notes |
|---|---|---|
| ext4 | resize2fs /dev/vg/lv |
Works mounted. Takes no size: fills the volume. |
| XFS | xfs_growfs /mount/point |
Takes the mount point, not the device. |
| Btrfs | btrfs filesystem resize max /mount/point |
Note the XFS asymmetry. resize2fs takes a device; xfs_growfs takes a mount
point. Getting them the wrong way round is a small, annoying, extremely common
error, and the reason is that XFS can only be grown while mounted so it works in
terms of the mount.
Or skip the second step entirely by asking lvextend to do it:
lvextend -L +500M --resizefs /dev/data/web
-r is the short form. It calls the right resize tool for the filesystem it
finds. Use it. The only reason to do the two steps separately is to understand
what -r is doing, which is what the capture above is for.
And -l +100%FREE is the other flag worth memorising. It takes everything
left in the group rather than a number you had to work out:
lvextend -l +100%FREE -r /dev/data/web
If you already administer Linux: snapshots, thin pools, pvmove, and the devices file
Snapshots are copy-on-write and are the reason to reach for LVM even on a
single disk. lvcreate -s -n web-snap -L 2G /dev/data/web gives a frozen view
for the duration of a backup, so a database dumps consistently without going
offline. The trap is capacity: a snapshot stores changed blocks, and when it
fills it is dropped, invalidating the backup in progress. Size it for the
write volume during the window, monitor lvs for the Data% column, and
delete it as soon as the backup finishes, a forgotten snapshot degrades write
performance indefinitely.
Thin provisioning (lvmthin) lets the sum of volume sizes exceed the pool, on
the assumption they will not all fill. That is a genuinely useful trade for
virtual machine images and a genuinely dangerous one otherwise: when a thin pool
fills, every filesystem on it takes I/O errors at once, and recovering is far
harder than having said no in the first place. Monitor pool usage, set
autoextend thresholds in lvm.conf, and treat the alert as urgent.
pvmove relocates extents off a physical volume while everything stays
online, which is how you retire a failing disk: pvmove /dev/sdb, then
vgreduce data /dev/sdb, then pvremove. It is slow and restartable, and it is
one of the genuinely impressive things LVM does.
The devices file (/etc/lvm/devices/system.devices, visible in the capture
above) replaced global_filter on recent releases. LVM now only considers devices
listed in it, which stops it scanning every disk on the machine. Add one with
lvmdevices --adddev, and know that this is why a disk from another machine may
not show up in pvs until you do.
Striping and mirroring exist (lvcreate -i 2 stripes across two PVs, -m 1 mirrors) but mdadm is the more common answer for redundancy, and that is
the next lesson. LVM on top of mdadm is the usual arrangement: mdadm handles
the disks failing, LVM handles the sizes changing.
Shrinking is possible for ext4 and Btrfs and never for XFS, and the order
reverses: filesystem first, then volume. Shrink the volume first and you have cut
the end off a filesystem that was still using it. lvreduce -r gets the order
right for you, and doing it by hand is one of the few places where reading the
command twice is genuinely warranted.
Across distributions
| RHEL family | Debian family | |
|---|---|---|
| LVM by default on install | Yes | No, unless chosen |
| Package | lvm2 |
lvm2 |
| Typical root device | /dev/mapper/rhel-root |
/dev/sda1 |
| Root filesystem | XFS, so xfs_growfs |
ext4, so resize2fs |
The two defaults compound. A RHEL-family machine is LVM plus XFS, so growing
root means lvextend then xfs_growfs <mount point> and shrinking is impossible.
A Debian machine is usually a plain partition plus ext4, so growing root means
repartitioning first. Same task, entirely different procedure, and assuming the
one you know is how people get stuck.
If you already administer Linux: what to check before you trust an LVM machine you inherited
Four things, none of which show up in df.
Is anything thin-provisioned? lvs -o +lv_layout,pool_lv names the
layout. A thin pool can be over-committed, and when it fills, every filesystem
on it takes I/O errors simultaneously. lvs on a thin pool shows Data% and
Meta% columns, and metadata exhaustion is the failure people miss, a
pool with free data space and full metadata is just as broken.
Are there forgotten snapshots? A snapshot with a Data% climbing toward 100
will be dropped when it fills, silently invalidating whatever it was taken for.
One left behind after a backup degrades write performance indefinitely, because
every write to the origin now copies the old block first. lvs shows them with
an s in the Attr column.
Does the volume group have room to breathe? vgs with VFree 0 means the
next capacity request needs a disk and a change window. Twenty per cent
unallocated is what turns a 2am alert into a one-line fix.
Is the devices file current? /etc/lvm/devices/system.devices replaced
global_filter on recent releases, and LVM only considers devices listed in it.
A disk moved from another machine will not appear in pvs until
lvmdevices --adddev is run, which presents as LVM refusing to see a disk that
is plainly there.
Prove it
Before a resize:
# Is there room in the pool
sudo vgs
# What is the volume now, and what is the filesystem
sudo lvs
df -h /mnt/web
After:
# The volume grew
sudo lvs
# And so did the filesystem. This is the one that matters.
df -h /mnt/web
df is the acceptance test, not lvs. lvs confirms the volume changed,
which is the step that never fails. df confirms the thing the application will
actually experience.
What trips people up
1. lvextend worked and df did not change
The whole subject of the prediction above. The volume grew; the filesystem did
not. resize2fs /dev/vg/lv for ext4, xfs_growfs /mount/point for XFS.
Use lvextend -r and it cannot happen.
2. “Insufficient free space”
lvextend refuses because the volume group has nothing left. vgs shows
VFree 0.
LVM does not create capacity; it manages it. You need a new disk: pvcreate /dev/sdc, then vgextend data /dev/sdc, and the pool grows. Then extend.
That three-command sequence (pvcreate, vgextend, lvextend -r) is the whole answer to “we need more space”, and it is worth being able to type from memory.
3. Mixing up the device and the mount point
resize2fs takes the device. xfs_growfs takes the mount point. lvextend takes
the device. df takes either.
There is no logic to remember here, only the fact. -r avoids it.
4. Forgetting LVM is there at all
df reports /dev/mapper/rhel-root and somebody goes looking for a partition of
that name. There is not one.
lsblk shows the whole stack at once (disk, partition, PV, LV) and is the
fastest way to orient on an unfamiliar machine.
5. Treating LVM as a backup or a redundancy layer
It is neither. A volume group spanning two disks loses everything when either disk fails, because extents are spread across both and nothing is duplicated. That is strictly worse than two separate disks.
Redundancy is the next lesson. LVM is about flexibility, and combining the two is the normal arrangement rather than a choice between them.
Work it through
A monitoring alert says /var/lib/mysql is at 97%. The server is live and the
database cannot be stopped.
$ df -h /var/lib/mysql
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/vg_data-mysql 200G 194G 6.1G 97% /var/lib/mysql
Reason it through before reading on.
Is this even LVM? Yes: /dev/mapper/vg_data-mysql names a volume
group and a logical volume. That single detail decides whether this is a
two-command fix or a maintenance window.
Second, is there room in the pool? sudo vgs. Two possibilities:
VFreeshows 300G. Somebody provisioned generously and left headroom. You are three minutes from done.VFreeshows 0. The pool is fully allocated, and you need a disk before you can do anything. That is a different conversation and it involves other people.
Which filesystem? findmnt /var/lib/mysql or lsblk -f. XFS on a
RHEL-family box, most likely, which decides the second command and rules out ever
giving the space back.
With free space in the group, the whole operation is one line:
sudo lvextend -L +100G -r /dev/vg_data/mysql
Volume grown, filesystem grown, database never stopped, no unmount. df confirms
it.
With no free space, add a disk and extend the pool first:
sudo pvcreate /dev/sdd
sudo vgextend vg_data /dev/sdd
sudo lvextend -l +100%FREE -r /dev/vg_data/mysql
Still online, still no outage.
Now the question worth sitting with: why can this be done live at all, when the same job on a plain partition would need an outage?
Because of what is not next to the volume. On a partitioned disk, growing
/dev/sda3 means the space immediately after it must be free, and it is
occupied by /dev/sda4. The only ways out involve moving /dev/sda4, which means
unmounting it.
Under LVM, the volume is a set of extents scattered wherever there was room. There is no “next to”. Adding extents does not disturb anything, so nothing has to be unmounted, so nothing has to stop.
The habit worth taking: on a new machine, look at lsblk before you need
it. Knowing whether you are on LVM, and how much VFree there is, converts a
2am capacity alert from an investigation into a command. And when you build
something, do not allocate the whole volume group, leaving 20% unallocated
is what makes that command available later.
Try it
Optional, on a virtual machine with two spare disks. Do not do this on anything you care about.
sudo pvcreate /dev/sdb /dev/sdc, thensudo pvs. Note the free space.sudo vgcreate testvg /dev/sdb /dev/sdc, thensudo vgs. CompareVSizeto the sum of the two disks and explain the difference.sudo lvcreate -n vol1 -L 1G testvg, thenlsblk. Say which physical disk it landed on and why.sudo mkfs.ext4 /dev/testvg/vol1, mount it,df -h.sudo lvextend -L +1G /dev/testvg/vol1and rundf -hagain before doing anything else. Confirm nothing changed.sudo resize2fs /dev/testvg/vol1, thendf -honce more.- Do it again with
lvextend -L +1G -rand note that step 5 disappears.
Verification step. You have it when you can grow a mounted filesystem in one
command and explain, without looking, why df would not have moved if you had
left the -r off.
Check yourself
Name the three LVM layers from the bottom up, and the command that creates each.
Physical volume, a disk or partition handed to LVM, created with
pvcreate. It stops being usable directly.
Volume group, a pool made from one or more physical volumes, created with
vgcreate. This is where capacity lives.
Logical volume, a slice taken from the pool, created with lvcreate. It
behaves exactly like a partition and is what you run mkfs on.
The reporting commands mirror them: pvs, vgs, lvs. vgs is the one to check
before any resize, because its VFree column answers whether the resize is
possible at all.
lvextend reports success and df is unchanged. What happened and what is the fix?
The volume grew and the filesystem did not. They are separate layers.
lvextend made the container bigger; the filesystem inside it is still using
exactly the blocks it was created with, so df, which reports the filesystem,
reports the same number.
Nothing failed. There is simply unclaimed space inside the volume now.
The fix depends on the filesystem: resize2fs /dev/vg/lv for ext4, xfs_growfs /mount/point for XFS. Note that one takes a device and the other takes a mount
point, which is a small piece of arbitrary trivia worth remembering.
Better: lvextend -r does both, picking the right tool for the filesystem it
finds, and removes the whole failure mode.
Why can an LVM volume be grown while mounted, when a partition cannot?
Because nothing is next to it. A partition is a contiguous region defined by a start and an end sector, so growing it requires the sectors immediately after it to be free, and they belong to the next partition. Freeing them means moving that partition, which means unmounting it.
A logical volume is a set of extents allocated from a pool. They need not be contiguous, need not be in order, and need not be on the same physical disk. Growing it means taking more extents from wherever there is room, which disturbs nothing.
Since nothing is disturbed, nothing has to be unmounted, and both ext4 and XFS support growing while mounted. The database never stops.
vgs shows VFree 0 and you need another 500 GB. What are the three commands, in order?
sudo pvcreate /dev/sdd
sudo vgextend vg_data /dev/sdd
sudo lvextend -l +100%FREE -r /dev/vg_data/mysql
pvcreate hands the new disk to LVM. vgextend adds it to the existing
pool, which is the step that makes VFree non-zero. lvextend -l +100%FREE -r
takes everything now available and grows the filesystem in the same breath.
LVM manages capacity; it does not create it. When the group is full the answer is always a new physical volume first.
All three run with the filesystem mounted and in use.
A volume group spans two disks and one of them fails. What happens, and what does that tell you about what LVM is for?
You lose the entire volume group, including volumes that appear to live entirely on the surviving disk. Extents are allocated from a single pool with no duplication, so half of the pool disappearing takes every filesystem in it with it.
That is worse than two independent disks, where a failure would have cost you one of them.
What it tells you: LVM is a flexibility layer, not a redundancy layer. It answers “how do I resize this without downtime”, not “how do I survive a disk failure”.
Redundancy is mdadm’s job, and the normal arrangement is both, RAID underneath handling failure, LVM on top handling size. They solve different problems and neither substitutes for the other, and neither is a backup.
References
- lvm(8) - Linux man-pages project. Accessed 2026-08-07.
- lvcreate(8) - Linux man-pages project. Accessed 2026-08-07.
- lvresize(8) - Linux man-pages project, for the sign convention and the
-roption. Accessed 2026-08-21. - lvchange(8) - Linux man-pages project, for activation. Accessed 2026-08-21.
- lvextend(8) - Linux man-pages project. Accessed 2026-08-07.
- vgcreate(8) - Linux man-pages project. Accessed 2026-08-07.
- resize2fs(8) - Linux man-pages project. Accessed 2026-08-07.
- xfs_growfs(8) - Linux man-pages project. Accessed 2026-08-07.
- lvmthin(7) - Linux man-pages project. Accessed 2026-08-07.
Every block above with a distribution and architecture header was captured by running the command on an AlmaLinux 10.2 container. Blocks without one are illustrative.