Last week I opened the Windows Storage settings and noticed something weird: my C: partition was sitting at 100%, with only a handful of gigabytes free. I hadn't installed anything huge. No games, no video files, no big database migrations. The only thing I'd been doing for the previous weeks was running a vLLM server inside WSL2.
A quick dir on AppData pointed at the culprit right away:
C:\Users\<me>\AppData\Local\WSL\Ubuntu\ext4.vhdx 55.02 GB
55 GB. For an Ubuntu install that, by all internal accounts, only held about 30 GB of real data.
The mystery
WSL2 stores each Linux distribution inside a single virtual hard disk file (ext4.vhdx) on the Windows side. That file is supposed to be sparse: the distro thinks it has a 250 GB disk available, while the host file only consumes physical bytes for the data actually written to it. Writes go in, blocks get allocated; deletes plus a periodic fstrim should release them back to the host.
That's the theory. In practice, WSL2 ships the VHDX as a fully-provisioned file, not a true NTFS sparse one. You can confirm it with one command:
> fsutil sparse queryflag "$env:LOCALAPPDATA\WSL\Ubuntu\ext4.vhdx"
This file is NOT set as sparse
Without the sparse flag, every byte ever written to the VHDX stays allocated on the Windows side forever—even after the corresponding file is deleted inside Linux. The VHDX grows, but it never shrinks.
That explained my numbers exactly: df -h inside the distro reported 29 GB used, but the host file was 55 GB. There were ~26 GB of free space inside the Linux filesystem that had become permanently pinned in the VHDX.
The fix: --set-sparse true
WSL has a flag for this. It's been sitting in wsl --help since 2024, but it's not exactly advertised:
wsl --shutdown
wsl --manage Ubuntu --set-sparse true --allow-unsafe
Two things to notice here:
--allow-unsafeis mandatory. Microsoft disables the flag by default because of an old data-corruption bug in early WSL2 builds. Modern WSL2 doesn't have that bug, but the safety latch is still on. If you forget--allow-unsafeyou'll get a polite refusal about “potential data corruption”—it's not a permissions thing, just a Microsoft policy.- You may need
wsl --shutdownfirst. If the distro is running,wsl --managereturnsERROR_SHARING_VIOLATIONand tells you to shut it down. Re-run after the shutdown.
After this, the VHDX is a real NTFS sparse file:
> fsutil sparse queryflag "$env:LOCALAPPDATA\WSL\Ubuntu\ext4.vhdx"
This file is set as sparse ✓
The WSL-native reclaim path: fstrim
Once sparse is on, the WSL-native way to release unused space is the same one Linux has used for a decade:
sudo fstrim -v /
# /: 222.1 GiB (238447005696 bytes) trimmed
That tells the kernel “these ext4 blocks are free”, and the VHDX driver hands those hints back to NTFS, which deallocates them from the host file. No shutdown of the distro, no admin elevation, no scheduled task—just an in-place operation.
A few things worth knowing if you're going to rely on this:
- Ubuntu's weekly
fstrim.timerdoesn't fire inside WSL by default. systemd ships it withConditionVirtualization=!container, and WSL2 reports itself as a container. You have to invokefstrimmanually, or override the timer withsystemctl edit fstrim.timer. I prefer manual, so I leave it alone. wsl --manage <Distro> --compactdoes not work once sparse is on. It fails with “Virtual hard disk files ... must not be sparse”—that's a Hyper-V API constraint, not a WSL bug. You don't need--compactanyway once sparse mode is on, because the file tracks your data automatically.diskpart compact vdiskworks, but it forces the VHDX back into non-sparse mode. You'd then have to re-enable sparse after. It's the legacy approach. Pick one path or the other; don't mix.
Putting it in a one-liner
I keep this in ~/Scripts/compact-wsl.ps1 and call it whenever I want peace of mind:
$vhdx = "$env:LOCALAPPDATA\WSL\Ubuntu\ext4.vhdx"
$before = (Get-Item $vhdx).Length
wsl -d Ubuntu -- bash -c 'sudo fstrim -v /'
$after = (Get-Item $vhdx).Length
"{0:N2} GB -> {1:N2} GB ({2:N1} GB reclaimed)" -f `
$before/1GB, $after/1GB, ($before-$after)/1GB
No shutdown. No admin. No scheduled task. Just a thin wrapper around the WSL-native reclaim path.
Results
Here's what actually happened on my machine:
| Step | VHDX size |
|---|---|
| Before any change | 55.02 GB |
After one wsl --manage Ubuntu --compact | 32.4 GB |
| After enabling sparse mode | 30.26 GB |
Wrote 40 GB inside WSL, deleted, fstrim | 30.26 GB |
The file stopped growing the moment sparse mode went on. Writing 40 GB of test data and deleting it didn't move the needle, because NTFS was already holding ~250 GB of virtual space “for free” through the sparse flag—and fstrim was happily returning blocks to the host as I deleted files.
TL;DR
- WSL2 VHDX files grow on the host but don't shrink unless they're true NTFS sparse files.
- Check:
fsutil sparse queryflag <path>. - One-time fix:
wsl --shutdown && wsl --manage <Distro> --set-sparse true --allow-unsafe. - Day-to-day reclaim:
sudo fstrim -v /inside the distro. No shutdown, no admin. - Don't bother with
diskpartorwsl --manage --compactonce sparse is on—they're mutually exclusive with it.
One flag, one fstrim, and 25 GB back on your C: drive.
If you found this useful and want to read the actual digging that led to it: I noticed the gap when my C: drive hit 100% during a routine vLLM session. There was no error, no warning—just an OS quietly running out of space because of a default that should have been the other way around.