Where Did My Disk Go?

Reclaiming Space Ubuntu Hid From Me


I put a 240GB drive in my always-on NAS box, ran a stock Ubuntu 24.04 Server install, clicked through the disk step like a reasonable person, and then a few weeks later ran df -h and saw a root filesystem of 98GB.

If you’ve done a default Ubuntu Server install on LVM, this may have happened to you too. Here’s why, and the two commands that fix it.

The symptom

$ df -hT /
Filesystem                        Type  Size  Used Avail Use% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv ext4   98G  7.3G   86G   8% /

A 240GB disk. A 98GB root filesystem.

Why this happens

On a plain, single-disk machine, your filesystem sits directly on a partition and takes the whole thing. Simple. But the Ubuntu Server installer uses LVM by default, and LVM inserts a couple of layers of abstraction between the disk and your files. That is what gives you the flexibility to resize things live and take snapshots.

Three layers, bottom to top:

  1. Physical Volume (PV) – the actual partition on the disk. This is /dev/sda3, and it does span the full drive.
  2. Volume Group (VG) – a pool of storage built from one or more PVs. Mine is ubuntu-vg.
  3. Logical Volume (LV) – a flexible slice carved out of the VG. This is where the space went. The installer carved out a 100GB LV and left the remaining 135GB in the group, unclaimed. Your filesystem lives inside this LV and cannot see outside of it.

The disk was full-size. The pool was full-size. The slice was small.

Step 1: Diagnose

Before touching anything, you need to answer one question: where is the free space? The answer sends you down one of two very different paths, and running the wrong path’s commands is how people end up confused and stuck.

Three commands tell you everything:

sudo vgs      # how much free space is in the volume group? (the VFree column)
lsblk         # how is the disk physically laid out?
df -hT        # what filesystem are we on? (ext4 vs xfs changes the last step)

Here’s what mine said:

$ sudo vgs
  VG        #PV #LV #SN Attr   VSize   VFree
  ubuntu-vg   1   1   0 wz--n- 235.42g 135.42g

$ lsblk
NAME                      MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda                         8:0    0 238.5G  0 disk
├─sda1                      8:1    0     1G  0 part /boot/efi
├─sda2                      8:2    0     2G  0 part /boot
└─sda3                      8:3    0 235.4G  0 part
  └─ubuntu--vg-ubuntu--lv 252:0    0   100G  0 lvm
  • VFree is 135.42g — the free space is already inside the volume group, ready to hand out.
  • sda3 is 235.4G and already contains the LVM — the partition already spans the whole disk.

Step 2: The two commands to fix it

This is the part I wanted to understand rather than just paste, because it’s two commands and it wasn’t obvious to me why one wouldn’t do.

  • the logical volume (the slice) needs to get bigger, and
  • the filesystem inside it needs to be told to spread out into the newly-available room.

Growing the slice doesn’t automatically grow its contents. So:

First, extend the logical volume. I chose to leave ~50GB unclaimed rather than take all 135GB

sudo lvextend -L +85G /dev/ubuntu-vg/ubuntu-lv

To claim all remaining free space instead, the idiom is sudo lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv. Note the lowercase -l (extents / percentages) versus uppercase -L (absolute sizes like +85G). Two different flags; easy to fat-finger.

At this point the LV is bigger, but df would still report the old size — because the filesystem hasn’t been told anything changed. It’s a bigger box with the same-size contents rattling around inside.

Second, grow the filesystem to fill the LV. Mine is ext4, so:

sudo resize2fs /dev/ubuntu-vg/ubuntu-lv

Both commands run live. The root filesystem stays mounted, the box stays up, services keep serving. No reboot, no rescue USB, no downtime.

Confirm it worked:

$ df -hT /
Filesystem                        Type  Size  Used Avail Use% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv ext4  182G  7.3G  166G   5% /

Step 3: Why I left 50GB on the table

I only just now learned LVMs can take snapshots. An LVM snapshot is a frozen, point-in-time view of a volume that you create before doing something risky: a big package upgrade, an experiment in /etc/fstab, anything you’d want an undo button for. If it goes well, you throw the snapshot away and keep your changes. If it goes badly, you roll back to the snapshot and it’s as if the risky thing never happened.

And how is 50G able to backup 185G of data?

The trick is copy-on-write (CoW). When you create the snapshot, LVM copies nothing. It just writes down a marker: “here is what ubuntu-lv looks like at this instant.” The snapshot volume starts essentially empty. Reading the snapshot right after you make it works by reading the original volume—they’re identical, so there’s nothing to store separately.

The 50G only starts filling as things change. Here’s the mechanism:

  • Something modifies a block on the live filesystem: you edit a file, a package upgrade rewrites a binary.
  • Before that write lands, LVM copies the old version of just that block into the snapshot’s reserved space, then lets the write proceed on the origin.
  • Now the origin has the new data, and the snapshot’s space holds the one old block needed to reconstruct “how it looked at snapshot time.”

Today I learned…


Posted

in

by

Tags: