Skip to content
WSLDeep Dive Published Updated 8 min readViews unavailable

Diagnosing ext4 Inode Exhaustion Inside a WSL 2 Distribution

Distinguish inode exhaustion from VHDX capacity problems in WSL 2, find file-heavy directories safely, and verify recovery without risking ext4.

A WSL 2 distribution can report free disk space and still fail to create a file. One cause is inode exhaustion: the ext4 filesystem has no free inode available for another file or directory even though it has unallocated data blocks. Expanding the virtual disk does not automatically add inodes to an existing filesystem. Confusing these counters can waste time, trigger unnecessary VHDX operations, or lead an operator to remove the wrong data.

WSL storage has at least two capacity layers: free blocks inside the distro’s Linux filesystem, and space allocated or available to the Windows-side virtual disk and host volume. Inode availability is a third, separate constraint. Diagnose all three before choosing a repair.

Check block and inode availability independently

Run both filesystem views against the affected path:

df -hT /
df -i /
df -hT /var
df -i /var
findmnt -T /var

df -h reports data-block capacity in human-readable units. df -i reports inode counts and use. A filesystem with 100 percent inode use can reject new directory entries while still showing many gigabytes free. A full filesystem can also show free inodes but no room for file data or metadata. Capture the mount point and filesystem type because a path may be on a separate mount.

If only one directory reports pressure, inspect which filesystem contains it with findmnt and df. Containers, package caches, build trees, language package stores, browser profiles, and mail queues can create many small files. The number of files matters more than their total bytes for inode consumption. Hard links add directory entries but refer to an existing inode; copying a file creates another inode.

Understand ext4 inode geometry

Ext4 allocates inodes according to filesystem geometry, including inode tables across block groups. At any point, the filesystem has a finite inode total; it does not create one new inode each time it allocates another data block. The initial count depends on the format options and bytes-per-inode ratio. The e2fsprogs documentation notes that resizing an ext filesystem changes its inode count to maintain that ratio, so a filesystem expansion can add inode capacity even though simply growing the backing VHDX does not.

This explains why the following can all be true at once: the Windows VHDX is dynamically expanding, the host NTFS volume has free space, df -h shows free blocks in ext4, and a Linux application receives ENOSPC while creating a file. ENOSPC is a symptom that needs both block and inode measurements; it does not identify which resource is depleted.

Do not try to fix inode exhaustion by compacting a VHDX. Compaction reclaims host-side allocation from free filesystem blocks; it does not create new inode table entries. Growing a VHDX changes the backing device size, not the mounted ext4 filesystem. If the supported WSL workflow expands both the virtual disk and ext4, check df -i after the operation rather than assuming it succeeded or that its result was proportional to the disk’s byte growth. The linked resize guide covers the supported operation; do not resize a live root filesystem with guessed device paths.

Find file-heavy directories without making the incident worse

Start with known application paths. A broad recursive scan of a busy root filesystem can add I/O load and traverse pseudo-filesystems unless constrained. Use filesystem boundaries and prune virtual trees:

sudo find / -xdev   -path /proc -prune -o -path /sys -prune -o -path /dev -prune -o   -type f -printf '%h
' 2>/dev/null |
  sort | uniq -c | sort -nr | head -30

This GNU find command counts regular-file parent directories on one filesystem. It can still take time on a large tree and does not count every inode type, such as directories, symlinks, sockets, and device nodes. Use it as a hotspot finder, then inspect a bounded subtree. A directory with many immediate children is different from a tree with many nested files.

For a narrower investigation, count entries in an application-owned path:

find /var/cache/example -xdev -mindepth 1 -printf '.' 2>/dev/null | wc -c
du -xhd1 /var/cache 2>/dev/null

The first command approximates directory-entry count in that subtree; du measures data blocks. Neither substitutes for df -i, and neither shows which files are safe to delete. Use the owning application’s cleanup or retention procedure rather than deleting files by age without understanding locks and state.

Separate distro, data disk, and host-volume pressure

A WSL distribution’s root filesystem normally resides in its own virtual disk. An additional WSL VHDX data disk has its own Linux filesystem and inode counters. Check the affected path’s mount source before repairing or growing any disk. A full data disk does not mean the root distro filesystem is full, and the reverse is also true.

On the Windows side, record the distro identity, WSL version, virtual disk location only through supported tooling, and host volume free space. Do not edit private package directories or move an open VHDX behind WSL’s back. If the host volume is full, freeing Linux files may reduce logical filesystem use but not immediately reduce the dynamic VHDX’s allocated host blocks. That is a separate compaction topic after the application and filesystem are healthy.

Avoid running e2fsck on a mounted distro filesystem. If the filesystem itself reports errors, follow a clean shutdown, backup, offline attach, and repair procedure documented for WSL. Inode exhaustion alone is not evidence of ext4 corruption and is not a reason to run repair tools.

Recover safely by removing or relocating owned files

When df -i confirms exhaustion, determine which service owns the file-heavy directory. Stop or quiesce that service if its cleanup procedure requires it. Use package-manager cache cleanup, application retention settings, log rotation, or the application’s own garbage collector. Moving files to another filesystem can fail if the process expects atomic rename semantics, so use a supported migration path and verify ownership and permissions.

After cleanup, rerun df -i and confirm a new file can be created in the affected filesystem. Check the service’s health and logs, then validate normal writes under the application’s account. If the inode count remains unchanged at 100 percent, the deleted files may have been held open by running processes, cleanup targeted a different mount, or the file system path is not the one measured. Inspect open deleted files with lsof if available and restart only the owning process after saving evidence.

If future growth repeatedly exhausts inodes, evaluate a new filesystem geometry or a separate data filesystem using supported migration and backup procedures. Reformatting an existing distro VHDX destroys its filesystem contents. Do not casually change mkfs inode ratio or attempt to transplant an inode table while the distro is in use.

Plan around workload shape, not only bytes per file

An inode planning exercise should estimate the number of objects, not just total data size. A package cache with hundreds of thousands of tiny metadata files can pressure inodes while using little space; a large media file has the opposite shape. Measure both counters during the busiest normal build, deployment, or package update and retain enough headroom for temporary files and recovery operations.

If a separate data filesystem is considered, choose its format and inode geometry before copying the workload. Creating a new filesystem with a different bytes-per-inode ratio is a planned migration with a full restore test, not a quick command to run on the distro’s only disk. Preserve ownership, extended attributes, symlinks, hard links, and application consistency according to the data type. Verify the new mount with df -i and a representative application operation before removing the prior copy.

Acceptance and monitoring

A production readiness check records df -hT and df -i for the root and application data mounts, filesystem identity, host volume free space, and the application’s expected small-file growth. Alerting should distinguish block utilization from inode utilization; a block-only alert misses this failure mode. Set thresholds based on the workload’s growth rate and the time required to clean up or migrate data.

After a cleanup or migration, test file creation as the service user, not just root. Verify a representative application operation, filesystem free inodes, and logs. Repeat after package updates, container builds, or cache-heavy jobs that historically create many small files. Keep the recovery runbook explicit about which mount owns the files and what cleanup command is safe for that application.

Decision rule

If df -i is exhausted, target file-count growth and inode planning. If df -h is exhausted, target block usage. If Linux reports free blocks and inodes but Windows cannot grow the VHDX, investigate host-volume capacity or virtual-disk limits. If filesystem metadata errors appear, stop writes and use an offline, backed-up repair procedure. Those are different incidents and should not share one destructive “disk full” fix.

Related:

Sources:

Comments