kmail.at
← learning

sysadmin · difficulty ◆◆

lsblk — every disk, partition and mount, as a tree

df tells you a filesystem is full. lsblk tells you which physical disk, which partition and which LVM volume that fullness is sitting on.

The volume that filled up at 03:00 is an LVM logical volume, on a partition, on one of the NVMe disks in the box — and df calls all of it simply '/'. One command draws the whole stack, indentation and all.

2026-10-12 · 6 min read

$ lsblk

What it does

lsblk does not read /proc/mounts, and it does not read fstab. It reads /sys. The command walks /sys/block, resolves every device through /sys/dev/block by its major:minor number — that sysfs directory arrived in kernel 2.6.27, October 2008 — and then reconstructs the parent/child relationships the kernel already keeps: a partition belongs to a disk, an LVM logical volume belongs to its physical volume, a dm-crypt mapping belongs to the container underneath it. The output is that graph as a tree, and the indentation is the point. Default columns are NAME, MAJ:MIN, RM, SIZE, RO, TYPE and MOUNTPOINTS; RAM disks are filtered out for you. You do not need root — every command on this page was run as uid 1000 on Ubuntu 24.04 with util-linux 2.39.3. Root is only required when the udev database is missing, because then lsblk has to open each block device itself to read the label, UUID and filesystem type. That dependency is worth remembering: the tree, the sizes and the types are pure sysfs and always there, while FSTYPE, LABEL, UUID and FSUSE% come from udev, so a disk attached three seconds ago can print with those columns empty. The man page's own remedy is udevadm settle before lsblk.

The three questions I bring to it

What exists: how many physical disks, what model, which serial — the inventory I need before an RMA or a capacity review, and it comes out one line per disk. How it stacks: when something hands me a /dev/mapper/ubuntu--vg-ubuntu--lv and asks how much room is left on the box, the answer is three levels up, and lsblk -s prints that path upwards in one go. What is mounted where: mountpoints and fullness in the same view as the device topology, so I can see that the filesystem at 74% is the only thing on a 465.8G disk with no second partition left to grow into. The friction is noise: snap and Flatpak packages create a loop device each, so a plain lsblk on a desktop prints twenty lines of squashfs before your actual disk. Loop devices are major number 7, and -e7 drops them. That is a filter on a number the kernel assigns, not a filter on the word 'loop', which is why the same switch removes them from the tree instead of hiding them at the bottom.

Where it stops

lsblk knows nothing about inodes — df -i still answers that one. It cannot tell you which process holds a device open or which log grew; that is lsof and fuser. It says nothing about network filesystems, and FSAVAIL/FSUSE% only exist for what is currently mounted, so an unmounted backup disk shows a size and a blank usage column. The percentages are whole numbers: 74% of a 462.7G filesystem is 74% rounded, and if you are chasing the last gigabytes then df -h with its 1024-block arithmetic is not the answer either — lsblk -b gives exact bytes, which is what a script should compare anyway.

Example

$ lsblk -e7
NAME                      MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
nvme0n1                   259:0    0 465.8G  0 disk 
├─nvme0n1p1               259:1    0     1G  0 part /boot/efi
├─nvme0n1p2               259:2    0     2G  0 part /boot
└─nvme0n1p3               259:3    0 462.7G  0 part 
  └─ubuntu--vg-ubuntu--lv 252:0    0 462.7G  0 lvm  /

The version I actually type. -e7 excludes loop devices (major number 7) and the snap noise disappears, leaving one 465.8G NVMe disk, three partitions, and the LVM logical volume that carries the root filesystem. Read the indentation: nvme0n1p3 has no mountpoint of its own, because / lives on the LV that sits on top of it. MAJ:MIN is how the kernel addresses each device — 259:3 is the partition, 252:0 is the device-mapper volume — and that pair is what lsblk uses to look the device up in /sys/dev/block. Without -e7 this same box prints sixteen additional snap loop devices before the table even starts.

$ lsblk -e7 -o NAME,SIZE,FSTYPE,FSUSE%,FSAVAIL,MOUNTPOINTS
NAME                        SIZE FSTYPE      FSUSE% FSAVAIL MOUNTPOINTS
nvme0n1                   465.8G                            
├─nvme0n1p1                   1G vfat            1%      1G /boot/efi
├─nvme0n1p2                   2G ext4           11%    1.6G /boot
└─nvme0n1p3               462.7G LVM2_member                
  └─ubuntu--vg-ubuntu--lv 462.7G ext4           74%   96.5G /

Capacity triage in the shape I want it: device, size, filesystem, how full, how much is left, where it is mounted. The whole machine fits on six lines and the interesting answer is immediate — / is at 74% of 462.7G with 96.5G free, and the 462.7G partition below it has nothing left to give, because the LV consumed all of it. Blank cells are not errors: a whole disk and an LVM physical volume have no filesystem of their own, so FSTYPE, FSUSE% and FSAVAIL have nothing to report. Note the two different kinds of 'full' here. FSAVAIL is free space for unprivileged users, which on ext4 already excludes the 5% reserved for root — so a script comparing FSAVAIL against SIZE and expecting to reach 100% is measuring the wrong thing.

$ lsblk -s -e7 /dev/mapper/ubuntu--vg-ubuntu--lv
NAME                  MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
ubuntu--vg-ubuntu--lv 252:0    0 462.7G  0 lvm  /
└─nvme0n1p3           259:3    0 462.7G  0 part 
  └─nvme0n1           259:0    0 465.8G  0 disk 

The inverse view, and the one I reach for when a monitoring alert hands me a /dev/mapper name and nothing else. Instead of listing children, -s walks parents, so you start at the mounted LV and end at the physical hardware: LV → partition 3 → the 465.8G WD disk. That is the chain three separate jobs need — reading the SMART data of the right drive, deciding whether the disk can be replaced without touching the other partitions, and being able to say 'the database volume is on nvme0n1' out loud. It works on any device name you are handed: pass the partition and you learn which disk it belongs to, pass the LV and you get the whole stack.

$ lsblk -dno NAME,SIZE,MODEL,SERIAL
nvme0n1 465.8G WDS500G3X0C-00SJG0 2043A1478301

Hardware inventory: -d stops lsblk from printing children, -n drops the header row, -o picks the columns. One line per whole disk with the model and the serial number — which is exactly what a warranty claim, a smartctl query or a purchase order needs, and exactly what df cannot tell you at any price. On a machine with four identical 1.9T disks this is how I find out which one is which before pulling anything; add -p if you also want the /dev/ path. Note what is not here: no partitions, no mountpoints, no filesystem. -d is deliberately blind to everything above the hardware.

$ lsblk -J -e7 -o NAME,FSTYPE,UUID,MOUNTPOINTS | jq -r '.. | objects | select(.uuid) | [ .name, .fstype, .uuid, (.mountpoints // [] | join(",")) ] | @tsv'
nvme0n1p1	vfat	A839-4EBA	/boot/efi
nvme0n1p2	ext4	64e2ca88-9159-4a8f-9095-38a76b48e5c0	/boot
nvme0n1p3	LVM2_member	0TuIzL-IQMr-uVIF-8uLl-u0hD-iaxi-Cpt88O	
ubuntu--vg-ubuntu--lv	ext4	a70c862d-bf3a-4c70-88c6-947890e475ff	/

The scripting form: JSON in, tab-separated name/type/UUID/mountpoint out — the raw material for a regenerated fstab, a cloud-init template or an inventory table. Two things about the jq. First, lsblk's JSON is nested under children, so the filter starts with '..' to walk every object at any depth; a flat .blockdevices[] only sees the top-level disks and would miss all four rows above. Second, the '// []' guard is not decoration: a device with no mountpoint (nvme0n1p3 here) carries a null mountpoints field, and join on null dies with 'Cannot iterate over null'. I keep this filter in a file called lsblk.jq and run jq -rf lsblk.jq, because bash quoting around [" and "] gets old on the fourth retype.

Common flags

-e, --exclude <list>
Exclude devices by major number, comma-separated. The default is RAM disks (major 1), so plain lsblk never shows you /dev/ram*. Loop devices are major 7 — hence the lsblk -e7 reflex on any machine with snap or Flatpak installed. Verified: with LSBLK_DEBUG=all the trace prints 'FILTER: exclude: maj=7' and then 'loop1: ignore (by filter)'.
-o, --output <list>
Choose columns, or extend the default set with + — lsblk -o +UUID keeps the normal table and appends the UUID column. The man page warns in as many words that the default output is subject to change, so any time the output is parsed, name every column you depend on.
-f, --fs
Shorthand for NAME,FSTYPE,LABEL,UUID,FSAVAIL,FSUSE%,MOUNTPOINTS — the filesystem view in one letter. It is a display shortcut, not a second data source: the values still come from the udev database, so it can print blanks for a device that was just plugged in.
-s, --inverse
Print the stack upwards instead of the tree downwards: device, then its parent, then the whole disk. The fastest way to answer 'which physical disk is behind /dev/mapper/...' when all you have is the mapper name.
-d, --nodeps and -n, --noheadings
-d prints no children, -n prints no header row. Combined as -dno NAME,SIZE,MODEL,SERIAL they are the hardware inventory: one line per disk, no tree, nothing to grep away.
-b, --bytes
Sizes in exact bytes instead of 465.8G — 500107862016 for the disk above. Human-readable units are for humans; thresholds and comparisons in scripts belong in bytes, because '1.5T' and '1.6T' are one rounding away from being the same condition.
-J, --json / -P, --pairs / -y, --shell
Three machine-readable shapes: nested JSON, shell-style NAME="nvme0n1" FSTYPE="" pairs, and -y, which rewrites column names into shell-safe identifiers (FSUSE_PCT, MIN_IO). JSON output is a tree, so reach for .. in jq. Note the history in -y: pairs output got shell-safe names automatically in 2.37, and 2.38 made it opt-in again to stop breaking scripts that keyed off FSUSE%.

History

10 February 2011 — util-linux 2.19 lists lsblk under Release Highlights: 'this NEW COMMAND'

lsblk is not ancient. The 2.19 release notes put it third in the highlights, right after the project renamed itself back from util-linux-ng: 'lsblk(8): this NEW COMMAND lists information about all or selected block devices in tree-like format.' The changelog credits it as 'lsblk: add new utility [Milan Broz]', with Karel Zak adding --nodeps in the same cycle and renaming a column before release; the man page AUTHORS line still reads 'Milan Broz, Karel Zak'. Before that release, in February 2011, seeing a disk stack meant assembling it yourself from /proc/partitions, a hand-read of /sys/block, df, and lvs or pvs for anything logical — four tools, three formats, no tree. The same release moved partx onto libblkid, which is the tell: 2.19 was the point where util-linux started treating the block layer as something to read properly rather than something to scrape.

2.34 → 2.39 — the columns keep moving, and one of them changes meaning

lsblk has grown in small dated increments since, and the release notes are a good map of what breaks. 2.34 added --dedup and --merge for multipath and RAID setups. 2.37 is the one to remember: 'lsblk(8) provides new columns FSROOTS, and MOUNTPOINTS. The column MOUNTPOINTS is used in the default output now and this new column prints all mount points where the device is used (btrfs subvolumes, bind mounts, etc).' Until then one device printed exactly one mountpoint, and a bind mount or a btrfs subvolume was invisible to the tool. The singular MOUNTPOINT column never left — the man page still explains that it 'displays only one mount point (usually the last mounted instance of the filesystem)' — so old parsing keeps working while new scripts reach for the plural. Then 2.38 added --noempty, -y/--shell and the START column for partition offsets, and 2.39 added -N/--nvme, -v/--virtio and the ID, ID-LINK, PARTN and MQ columns. Which is why a script that hard-codes MOUNTPOINTS runs fine on this Ubuntu 24.04 box with util-linux 2.39.3 and comes back with an unknown-column error on a machine still on 2.32.

Fun facts

Pros & cons

pros

  • + Needs no root for the tree, the sizes, the types or the mountpoints, and it never touches a disk — all of it comes out of sysfs and the udev database, so running it on a busy production box is free
  • + Shows stacking that df and mount cannot: LVM volumes, dm-crypt mappings, partitions and loop devices as parents and children, with indentation that makes the relationship readable at a glance
  • + Machine-readable in three formats (-J, -P, -y) with stable, explicitly selected columns, which makes it the right source for fstab regeneration and hardware inventory scripts

cons

  • − Filesystem details depend on the udev database: right after hot-plugging a disk, FSTYPE, LABEL, UUID and FSUSE% can print empty until udevadm settle runs, which reads exactly like 'the disk is unformatted'
  • − Default columns are documented as subject to change, and they have changed — MOUNTPOINTS arrived in 2.37 and -y semantics were reworked in 2.38, so output-parsing scripts break across distros with no warning
  • − It answers block-device questions only: no inode usage, no knowledge of which process holds a device open, no network filesystems, and no usage figures for anything that is not currently mounted

Takeaways

  1. 1Type lsblk -e7, not lsblk. Loop devices are major number 7, and snap or Flatpak installs create one per package; the same switch removes them from the tree rather than pushing them below it.
  2. 2Before mkfs, dd or wipefs on any device, run lsblk -no MOUNTPOINTS <device> and require silence. A path in that output means you have the wrong device, and the check costs five seconds.
  3. 3Never parse the default output or -f. Name every column with -o, drop the header with -n, and pick -J or -P when something structured is needed — the man page explicitly refuses to guarantee default columns across versions.
  4. 4Use -s when you are handed a name and need a disk: lsblk -s /dev/mapper/... walks from the volume up to the hardware, which is the chain smartctl, warranty claims and replacement planning all need.
  5. 5For hardware questions use lsblk -dno NAME,SIZE,MODEL,SERIAL, and for size comparisons inside scripts use -b to get exact bytes instead of 465.8G.

Related commands