df, because df doesn't see inside the thin pool at all.
What a thin snapshot actually is
How a forgotten snapshot grows
- It allocates a fresh chunk from the pool's data space.
- If the write covers only part of the chunk, it copies the old chunk contents into the new one first. This is the copy-on-write step.
- It writes the new data into the fresh chunk.
- It updates the origin's mapping to point at the new chunk, and drops the old chunk's reference count to one.
What happens when the pool runs out
no_space_timeout parameter of the dm_thin_pool module, the queued writes fail with errors. Guests then see I/O errors, and filesystems like ext4 commonly remount read-only to protect themselves. A pool can be set to error immediately instead with LVM's --errorwhenfull option, which trades the freeze for faster failure.
When metadata space is exhausted, the pool can no longer record new mappings, so it typically drops into read-only or failed mode. Recovery may involve taking the pool offline and running lvconvert --repair, which is a much worse afternoon than deleting a few snapshots. Most of the effort here is about never getting near that point.
If you are chasing a slow VM rather than a full pool, the iowait side of that story is covered in High iowait in a Proxmox VM: Is It the Guest Disk or the Host Storage?.
Reading pool usage with lvs
lvs. On a default install the volume group is usually pve and the pool is data, but check your own storage configuration. This command is read-only:
BASH
# Pool and volume usage in volume group 'pve' (read-only)
lvs -o lv_name,lv_size,pool_lv,origin,data_percent,metadata_percent pve
# Include the hidden data and metadata sub-volumes
lvs -a pveTEXT
LV LSize Pool Origin Data% Meta%
data 800.00g 90.00 40.00
root 96.00g
snap_vm-100-disk-0_pre-upgrade 100.00g data vm-100-disk-0
vm-100-disk-0 100.00g data 60.00data line. Data% is how much of the pool's physical data space is allocated. Meta% is how full the metadata volume is. Watch both. Comfortable data usage with climbing metadata usage is a warning sign, not a pass. The storage summary in the web UI focuses on data usage, so lvs is the easier place to see metadata.
Per-volume Data% is easy to misread. It shows how much of that volume's virtual size is mapped, including chunks shared with other volumes. It does not show how much space a snapshot uniquely holds. That is why adding up every volume's percentage can exceed what the pool reports. There is no simple column for a snapshot's unique cost. The practical test is to delete it and watch the pool's Data% drop.
The Origin column ties each snapshot volume to its disk, and the volume name usually carries the snapshot name. Snapshot volumes are often inactive and may show no Data% until activated. Their age is easier to read from Proxmox itself.
Turning pruning into a routine
lvremove on snapshot volumes directly, since it leaves stale entries in the guest config.
BASH
# List snapshots for every VM on this node (read-only)
for id in (qm list | awk 'NR>1 {print1}'); do echo "== VM id"; qm listsnapshot "id"; done
# Containers use pct instead of qm
pct listsnapshot 200
# Delete one snapshot after confirming nobody needs it. This cannot be undone.
qm delsnapshot 100 pre-upgradepre-php-upgrade-2026-10, so a review doesn't turn into guesswork. Second, review the list on a schedule, weekly on busy nodes, and treat anything older than the change it protected as a deletion candidate. Third, alert on Data% and Meta% for the pool itself. A cron job running lvs --noheadings -o data_percent,metadata_percent pve/data and comparing against a threshold is enough. Pick thresholds that leave room to react, typically well before 90 percent.
LVM can also grow a pool automatically through thin_pool_autoextend_threshold and thin_pool_autoextend_percent in /etc/lvm/lvm.conf. It only works if the volume group has free extents, and default Proxmox installs often hand nearly everything to the pool. Check vgs before counting on it. Metadata can be grown manually with lvextend --poolmetadatasize when the volume group has room, which buys time but doesn't remove the reason it filled.
The short version: a thin snapshot costs nothing on day one and a little more every day the VM keeps writing. Check Data% and Meta% on the pool, not just df on the host. Delete snapshots through qm or pct once the change they protected is confirmed. Give every snapshot a name that tells the next person when it's safe to remove.