Extending a virtual disk in vSphere takes about fifteen seconds. The part that surprises people is what happens next: the guest reports exactly the same free space it had before, and it keeps reporting it until four more layers are told about the change.
Between the VMDK and the directory that ran out of space sit a disk device, a partition, an LVM physical volume, a logical volume and a filesystem. Each has its own idea of how big it is, and each is grown by a different command. Miss one and the extra space stays invisible.
This page is written as a lab. It builds one small Ubuntu Server VM, fills its root filesystem on purpose, extends the VMDK from 60 GB to 80 GB and then walks every layer up to df, with a check after each command. Every screenshot comes from that VM, so you can rebuild it and compare your screen with this one step by step.
Applies to: vSphere 7 and 8, with an Ubuntu Server guest installed with the default guided LVM layout and an ext4 root filesystem.
The chain, end to end
Five layers, five commands. This table is the whole lab in short form: if you already know the sequence and only need the commands, work down it.
| Step | Layer | Command |
|---|---|---|
| 1 | The VMDK in vSphere | Edit Settings, Hard disk 1 from 60 to 80 GB |
| 2 | The partition | growpart /dev/sda 3 |
| 3 | The LVM physical volume | pvresize /dev/sda3 |
| 4 | The logical volume | lvextend -L +20G /dev/ubuntu-vg/ubuntu-lv |
| 5 | The ext4 filesystem | resize2fs /dev/ubuntu-vg/ubuntu-lv |
Read the chain from the bottom up. vSphere makes the virtual disk bigger. The partition sda3 sits on that disk and has to be stretched into the new space. LVM keeps its own record of how big the partition is, so the physical volume is told next. Only then does the volume group have free space to hand to the logical volume, and only when the logical volume is bigger can the filesystem inside it grow. df reports the last layer, which is why it is the last number to change.
What LVM is: PV, VG and LV
LVM, the Logical Volume Manager, adds a layer between the partition and the filesystem. Instead of formatting sda3 directly, the installer handed it to LVM. LVM pools that space and cuts volumes out of the pool, and the filesystem lives in one of those volumes. That extra layer is why growing / takes three LVM-aware steps rather than one, and also why it can be done online at all.
Three terms cover everything on this page:
| Term | What it is | In this lab | Show it |
|---|---|---|---|
| PV, physical volume | A disk or partition handed over to LVM | /dev/sda3 | sudo pvs |
| VG, volume group | A pool built from one or more PVs, cut into equal chunks called extents | ubuntu-vg | sudo vgs |
| LV, logical volume | A slice of the pool that behaves like a partition; the filesystem lives here | ubuntu-lv | sudo lvs |

Read the picture from the bottom up. The partition sda3 becomes a PV. The PV is the only member of the pool ubuntu-vg. The pool gives 49G to ubuntu-lv and keeps 29G unassigned, and ubuntu-lv carries the ext4 filesystem that df reports. /boot on sda2 is outside LVM altogether, which is why it never appears in pvs, vgs or lvs.
If you come from Windows, the nearest picture is Storage Spaces: the VG plays the role of the storage pool, and an LV is a virtual disk carved out of it. As with a pool, a VG can take more than one PV, so a second VMDK can later join the same group and its space can go to the same LV.
The pool is counted in extents, 4 MiB each by default, and that is the unit LVM really works in. The lvextend output in step 5 shows it: ubuntu-lv goes from 7423 to 12543 extents, a difference of 5120 extents of 4 MiB, which is exactly the 20 GiB that was asked for.
Reading pvs, vgs and lvs
Each layer has its own short report command. All three are read-only and safe to run at any time.
# One line per physical volume: which VG it belongs to, its size and free space
sudo pvs
# One line per volume group: how big the pool is and how much of it is unassigned
sudo vgs
# One line per logical volume: which VG it lives in and how big it is
sudo lvs
This is the vgs line from the lab after pvresize, as it appears on the step 7 screenshot:
VG #PV #LV #SN Attr VSize VFree
ubuntu-vg 1 1 0 wz--n- <78.00g 49.00g
| Column | Meaning | Lab value |
|---|---|---|
VG | Name of the volume group | ubuntu-vg |
#PV | How many physical volumes it is built from | 1, only /dev/sda3 |
#LV | How many logical volumes are cut from it | 1, only ubuntu-lv |
#SN | How many LVM snapshots exist | 0 |
Attr | Flags: w writeable, z resizeable, n normal allocation | wz--n- |
VSize | Total size of the pool | just under 78G |
VFree | Space not yet given to any LV | 49G |
lvs prints LV, VG, Attr and LSize for a plain volume like this one; the remaining columns are for thin pools, snapshots and mirrors and stay empty. For a mounted root volume the Attr field typically reads -wi-ao----: w writeable, a active, o open, meaning something is using it, here the mounted filesystem. pvs follows the same pattern with PV, VG, Fmt, Attr, PSize and PFree.
lvs has no free-space column, because a logical volume is never partly free from LVM's point of view: free space lives in the volume group. That is why the question "is there room to grow the LV?" is answered by VFree in vgs, and the question "is the filesystem full?" by df. For a verbose, one-field-per-line view of the same data, use sudo pvdisplay, sudo vgdisplay and sudo lvdisplay.
Build the lab
One VM is used throughout. These are the values on the screenshots; use your own names and addresses if you like, but keep them consistent so the commands below still match.
| Setting | Lab value |
|---|---|
| Guest OS | Ubuntu Server, default guided storage layout with LVM |
| VM name and hostname | SRV-LNX-01 |
| User | zaur, with sudo rights |
| Virtual disk | Hard disk 1, 60 GB, seen by the guest as /dev/sda |
| Network interface | enp3s0 |
| Address | 10.0.0.21/24 on an isolated lab network, no gateway, no DNS |
During installation, leave storage on the default Use an entire disk with Set up this disk as an LVM group ticked, and tick Install OpenSSH server. The lab network has no gateway, so nothing can be downloaded later with apt: whatever the installer puts on the disk is what you work with.
Give the VM a static address with Netplan
Ubuntu Server configures the network through Netplan, which reads YAML files from /etc/netplan/. Work at the VM console in vSphere for this part, because SSH is not reachable until the address exists. Start by looking at what the installer left behind.
# List the existing Netplan files before adding one
ls -l /etc/netplan/
Now create the lab file. The tee form writes it in one paste, with no editor needed.
# Write /etc/netplan/01-lab-static.yaml: a fixed IPv4 address, no DHCP
sudo tee /etc/netplan/01-lab-static.yaml > /dev/null <<'EOF'
network:
version: 2
ethernets:
enp3s0:
dhcp4: false
dhcp6: false
addresses:
- 10.0.0.21/24
EOF
Line by line: enp3s0 is the interface name from ip link, dhcp4: false and dhcp6: false stop the VM waiting for a DHCP server that does not exist on this network, and addresses gives it one fixed address with a /24 mask. There is no routes and no nameservers section because the lab never leaves its own subnet. YAML is strict about indentation: two spaces per level, never tabs.
# Netplan warns when the file is readable by other users; only root needs it
sudo chmod 600 /etc/netplan/01-lab-static.yaml
# Check the syntax and render the configuration without applying it
sudo netplan generate
# Apply it with an automatic rollback if you do not confirm
sudo netplan try
netplan generate is the safe dry step: a typo in the YAML is reported here and nothing changes. netplan try then applies the configuration and waits, 120 seconds by default, for you to press Enter. If you do not confirm, for example because the change cut you off, it rolls back by itself. Once confirmed, check the result:
# The inet line should read 10.0.0.21/24
ip -4 address show enp3s0
/etc/netplan/ in name order and a later file overrides an earlier one. If the installer left a file such as 50-cloud-init.yaml that also configures enp3s0 with DHCP, it sorts after 01-lab-static.yaml and can win. If ip -4 address show enp3s0 does not show 10.0.0.21/24, open that file and remove the enp3s0 section from it.
Turn on SSH
# Start the SSH server now and at every boot
sudo systemctl enable --now ssh
# Should report active (running)
systemctl status ssh --no-pager
enable makes SSH start at boot, and --now also starts it immediately, so one command covers both. From here the rest of the lab can run over SSH from a Windows machine with an address in the same 10.0.0.0/24 subnet:
ssh zaur@10.0.0.21
The first connection asks you to accept the host key. Connect to a Linux server from Windows covers that prompt, keys and scp in detail.
Fill the disk and record the starting state
A freshly installed VM has plenty of free space, which makes the before and after pictures look identical. For the lab, fill the root filesystem with one large throwaway file so the problem is visible. Do this only on a lab VM.
# How much space is free right now? Read the Avail column
df -h /
# Allocate one large file instantly. Size it from Avail so / ends up around 90 percent
sudo fallocate -l 20G /var/tmp/lab-fill.img
fallocate reserves the blocks without writing data, so a 20 GB file appears in a second. The file lives in /var/tmp, which is on the root filesystem, and it is removed at the end of the lab. A fresh install on a 29G root uses only a few GB, so 20G of filler lands in the low 90s; on a different layout, pick a size a little below the Avail figure.
Now record the starting state. When a later step seems to do nothing, this is what you compare against.
# Every block device with its size, type, filesystem and mount point
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
# Type and usage of the root filesystem; -T adds the filesystem type
df -hT /

sda is 60G, sda3 is 58G, the root logical volume is 29G and / is 94% full.The lab output, with the snap loop devices and the CD drive sr0 left out:
NAME SIZE TYPE FSTYPE MOUNTPOINTS
sda 60G disk
├─sda1 1M part
├─sda2 2G part ext4 /boot
└─sda3 58G part LVM2_member
└─ubuntu--vg-ubuntu--lv 29G lvm ext4 /
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv ext4 29G 26G 1.7G 94% /
How to read it: sda is the whole virtual disk. sda1 is a 1M BIOS boot partition, sda2 is /boot as plain ext4, and sda3 is the LVM physical volume that carries the rest. Inside it sits one logical volume, ubuntu-lv, formatted ext4 and mounted as /. That last line is the one that is full.
# The logical volumes and the volume groups, with their free space
sudo lvs
sudo vgs
lvs shows ubuntu-lv at about 29G. vgs is the more interesting one: ubuntu-vg is about 58G in size, and its VFree column shows roughly 29G that no logical volume uses yet.
sudo vgs is the first command to run when / fills up: if VFree is not zero, lvextend and resize2fs alone may be enough and vSphere is not needed at all. This lab extends the VMDK anyway, to show the whole chain.
Step 1. Extend the VMDK
In the vSphere client, shut down SRV-LNX-01, right-click it and choose Edit Settings. On Virtual Hardware, change Hard disk 1 from 60 to 80 GB, click OK and power the VM on again.

This step changes the virtual hardware and nothing else. The partition table, LVM and ext4 inside the guest still describe a 60 GB disk, and they will keep doing so until each one is grown in turn.
Step 2. Check what the guest sees
Log in again over SSH and look at every layer at once.
# Confirm you are on the right machine and address
ip -4 address show enp3s0
# The disk should be bigger now; the partition should not
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
# The filesystem has not changed at all
df -hT /

sda is 80G, but sda3 is still 58G, the root volume still 29G and df still 94%.This is the gap the rest of the lab closes. The kernel read the new disk size at boot, so sda shows 80G. Every layer above it still shows its old size, because none of them grows by itself: 20G of unpartitioned space now sits at the end of the disk, after sda3.
echo 1 | sudo tee /sys/class/block/sda/device/rescan, then run lsblk again. Powering the VM off and on, as this lab does, makes that step unnecessary.
Step 3. Grow the partition
growpart rewrites the partition table so the chosen partition extends to the end of the free space behind it. It works on a mounted partition, and it ships in the cloud-guest-utils package, which was already installed on the lab VM. Check it is there, then run it.
# Prints the path if growpart is installed, nothing if it is not
command -v growpart
# Grow partition number 3 on /dev/sda into the free space after it.
# The disk and the partition NUMBER are two separate arguments, not /dev/sda3.
sudo growpart /dev/sda 3
CHANGED: partition=3 start=4198400 old: size=121628672 end=125827071 new: size=163573727 end=167772126
The numbers are 512-byte sectors. The start stays the same, which is what keeps the data safe, and only the end moves outward. Check the result:
lsblk

growpart /dev/sda 3 moves the end of the partition: sda3 goes from 58G to 78G.sda3 is now 78G. The root logical volume still shows 29G, because LVM has not been told yet.
NOCHANGE: partition 3 is size ... it cannot be grown means there is no free space directly after that partition. Either the disk was not extended, the guest has not seen the new size (go back to step 2), or sda3 is not the last partition on the disk. Use sudo growpart -N /dev/sda 3 for a dry run that reports what it would do without writing anything.
Step 4. Grow the LVM physical volume
LVM stores the size of each physical volume in its own metadata, which still says 58G. pvresize reads the partition again and updates that record.
# Tell LVM that /dev/sda3 is bigger now
sudo pvresize /dev/sda3
Physical volume "/dev/sda3" changed
1 physical volume(s) resized or updated / 0 physical volume(s) not resized
# The volume group should now show more free space
sudo vgs
VG #PV #LV #SN Attr VSize VFree
ubuntu-vg 1 1 0 wz--n- <78.00g 49.00g
VSize went from about 58G to about 78G, and VFree went from about 29G to 49G: the 29G the installer left unallocated plus the 20G just added. This is the moment the new space becomes usable. The < in front of 78.00g only means the value is slightly under 78 GiB after LVM metadata.
Step 5. Grow the logical volume
Now give part of that free space to the logical volume that holds /.
# Add exactly 20 GiB, the amount added to the VMDK, to the root logical volume
sudo lvextend -L +20G /dev/ubuntu-vg/ubuntu-lv
Size of logical volume ubuntu-vg/ubuntu-lv changed from <29.00 GiB (7423 extents) to <49.00 GiB (12543 extents).
Logical volume ubuntu-vg/ubuntu-lv successfully resized.
-L +20G means "add 20 GiB to the current size"; without the plus sign, -L 20G would mean "make it 20 GiB in total" and LVM would refuse because that is a shrink. The lab adds exactly what vSphere added and leaves the installer's 29G in reserve, which sudo vgs now shows as VFree. To take everything that is free instead, use -l +100%FREE, with a lowercase l because that value is counted in extents rather than in bytes.
/dev/ubuntu-vg/ubuntu-lv and /dev/mapper/ubuntu--vg-ubuntu--lv are the same logical volume under two names. The /dev/mapper form doubles every hyphen inside a name so it can use a single hyphen between the volume group and the volume. lsblk and df print the mapper form; the /dev/ubuntu-vg/ubuntu-lv form is easier to type, and both work in every command on this page.
Step 6. Grow the ext4 filesystem
The logical volume is 49G, but the ext4 filesystem inside it was built for 29G and still thinks it is that size. resize2fs grows it to fill the volume.
# With no size argument, resize2fs grows ext4 to fill the whole device.
# It takes the DEVICE, not the mount point /.
sudo resize2fs /dev/ubuntu-vg/ubuntu-lv
resize2fs 1.47.2 (1-Jan-2025)
Filesystem at /dev/ubuntu-vg/ubuntu-lv is mounted on /; on-line resizing required
old_desc_blocks = 4, new_desc_blocks = 7
The filesystem on /dev/ubuntu-vg/ubuntu-lv is now 12844032 (4k) blocks long.
"On-line resizing required" is information, not an error: ext4 can grow while mounted, and resize2fs did so. 12844032 blocks of 4 KiB is exactly the size of the logical volume, just under 49 GiB. The first line shows the version of the tools; yours may differ.
sudo lvextend -r -L +20G /dev/ubuntu-vg/ubuntu-lv does steps 5 and 6 in one command: -r runs the filesystem resize for you. Use it once the sequence is familiar. The lab keeps them separate so you can see which layer does what.
Step 7. Verify every layer
Run the same commands as at the start. Every layer should now report a new size.
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
df -hT /
sudo lvs
sudo vgs

pvresize, vgs, lvextend, resize2fs, then lsblk and df with / at 49G and 56%.NAME SIZE TYPE FSTYPE MOUNTPOINTS
sda 80G disk
├─sda1 1M part
├─sda2 2G part ext4 /boot
└─sda3 78G part LVM2_member
└─ubuntu--vg-ubuntu--lv 49G lvm ext4 /
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv ext4 49G 26G 21G 56% /
| Layer | Before | After | Grown by |
|---|---|---|---|
Virtual disk sda | 60G | 80G | vSphere |
Partition sda3 | 58G | 78G | growpart |
Volume group ubuntu-vg | about 58G, 29G free | about 78G, 29G still free | pvresize |
Logical volume ubuntu-lv | 29G | 49G | lvextend |
Filesystem on / | 29G, 94% used | 49G, 56% used | resize2fs |
/ mounted the whole time. If df still shows the old size, read the layers from the bottom up: the first one still showing an old number is the step to repeat.
Step 8. Clean up the lab
The filler file did its job. Remove it and check the space comes back.
# Remove the file created for the lab
sudo rm /var/tmp/lab-fill.img
# Used should drop by the size of the file
df -hT /
The VM is now a clean Ubuntu Server with an 80 GB disk and a 49G root. To repeat the lab from the beginning, the quickest route is a snapshot taken right after installation, reverted before step 1 and deleted once you are done, since a snapshot blocks the disk extend itself.
When there is no LVM
Some Ubuntu cloud images put ext4 straight on a partition, with no volume group in between. lsblk shows it immediately: the part line carries the / mount point and there is no lvm line under it. The chain is then shorter: extend the VMDK, grow the partition, grow the filesystem.
# Confirm there is no lvm layer: / is mounted directly on a partition
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
# Grow the partition that holds /, here partition 1, then the filesystem on it
sudo growpart /dev/sda 1
sudo resize2fs /dev/sda1
pvresize and no lvextend, and there is also no way to add space from a second disk later. That is the practical argument for LVM on a server that may grow again.
What goes wrong
| Symptom | Cause | Fix |
|---|---|---|
| Disk size field greyed out in vSphere | The VM has snapshots | Delete or consolidate, then extend |
lsblk still shows sda at 60G | The guest has not seen the new size | Rescan sda or power cycle |
growpart: command not found | cloud-guest-utils is missing | Install it where apt can reach a mirror |
NOCHANGE: partition 3 ... cannot be grown | No free space right after sda3 | Check lsblk, then step 2 |
lvextend reports insufficient free space | pvresize was skipped | Run step 4, check vgs |
resize2fs says Nothing to do! | Volume not extended, or wrong device | Check lvs, copy the path from lsblk |
Every layer is bigger except df | Step 6 did not run | resize2fs /dev/ubuntu-vg/ubuntu-lv |
netplan try rolled back | Not confirmed in 120 seconds | Run it again and press Enter |
SSH: Connection refused | SSH server not running | sudo systemctl enable --now ssh |
Tips and limitations
- Growing is online and safe. Shrinking is not: ext4 can only be shrunk while unmounted, and a root filesystem cannot be unmounted on a running system. Plan sizes upward only.
- Run
sudo vgsbefore asking for a bigger VMDK. On a default Ubuntu Server install the volume group often has unallocated space already, andlvextendplusresize2fsis the whole fix. - Take a backup or a snapshot before the first time you do this on a production VM, and remove the snapshot afterwards so the next extend is not blocked by it.
- Extending the VMDK consumes datastore space immediately on a thick disk and gradually on a thin one. Check the datastore, not just the guest.
- On a machine with several disks, match the
/dev/sdXto the VMDK you extended by size before runninggrowpart. Growing the wrong partition table is not something LVM will warn you about. - An MBR disk cannot go past 2 TB. If the disk needs to grow beyond that, it has to be converted to GPT or the space added as a second disk.
/bootonsda2is a plain partition outside LVM, and it is rarely the one that fills up. If it does, the fix is removing old kernels, not resizing.
Official documentation
- Change the Virtual Disk Configuration: VMware vSphere 8.0, Broadcom TechDocs
- netplan try: Netplan documentation
- growpart: Ubuntu manpage
- pvresize: Ubuntu manpage
- lvextend: Ubuntu manpage
- resize2fs: Ubuntu manpage
Related tools
- PowerCLI Command Builder - build the PowerCLI call when you would rather extend the disk from a script than from the UI.
Related guides
- Get-VMGuestDisk: guest disk free space with PowerCLI - find which VMs are about to need this before they page you.
- Connect to a Linux server from Windows - how to get to the shell where all of this runs.
- Linux commands cheat sheet - the surrounding commands for disks, mounts and space.
- Linux cheat sheet for Windows admins - the same layers, mapped from
diskpartand Windows storage terms. - The most useful ESXi and vCenter folders, tools and logs - where to look when the storage side is the problem.
- Windows disk and storage cheat sheet - the equivalent chain on a Windows guest.
Five cheat sheets, one PDF
Subnet masks, PowerShell, Linux commands, HTTP status codes and the ESXi command line - one page each, free to keep. Leave an address and it arrives in a minute.