How to Free Up C: Drive Space on Windows by Cleaning WSL Crash Dumps

7 min read
23 views

One day your Windows machine is fine. The next, File Explorer shows a red bar and a system drive with almost no room left. If you run WSL2 (the Windows Subsystem for Linux, which lets you run real Ubuntu inside Windows) and Docker, there is a common hidden cause: crash dump files that quietly pile up in your temp folder.

This guide shows you how to free up C: drive space on WSL setups by finding what is actually eating it, cleaning the safe junk without touching real data, and setting up a small script so it never sneaks up on you again. The examples are run on WSL2 Ubuntu in Windows, but the cleanup logic works the same on any Windows 10 or 11 machine.

Prerequisites

  • A Windows 10 or 11 machine with WSL2 installed
  • Basic comfort running commands in PowerShell
  • Docker Desktop (optional, only if you also want to clean Docker images)
  • If WSL itself is throwing startup errors on top of the disk problem, fix that first with How to Fix WSL CreateVm/HCS/ERROR_FILE_NOT_FOUND on Windows

Step-by-Step Guide

Step 1: Measure before you delete

Never start deleting blindly. The smart move is to size your top-level folders first, then drill into the biggest one. Think of it like checking which room in the house is full before you start throwing things out. Open PowerShell and run:

Get-ChildItem C:\ -Directory -Force -ErrorAction SilentlyContinue | ForEach-Object {
    $size = (Get-ChildItem $_.FullName -Recurse -File -Force -ErrorAction SilentlyContinue |
             Measure-Object Length -Sum).Sum
    [PSCustomObject]@{ Folder = $_.FullName; SizeGB = [math]::Round($size/1GB, 2) }
} | Sort-Object SizeGB -Descending | Select-Object -First 10
  • Get-ChildItem C:\ -Directory: lists the folders at the root of your C: drive
  • Measure-Object Length -Sum: adds up the size of every file inside each folder
  • Sort-Object SizeGB -Descending: puts the biggest folders at the top

On the machine in this example, the result pointed straight at the user profile:

FolderSize
C:\Users279 GB
C:\Windows57 GB
C:\Program Files14 GB

C:\Users was the whale. Following the trail into AppData\Local showed two surprises: a Packages folder at 110 GB (that is your WSL Linux disk) and a Temp folder at 90 GB. A 90 GB temp folder is not normal. That is usually the sign that something is crashing in a loop.

Step 2: Find the real culprit in %TEMP%

Break down what is inside the temp folder the same way:

Get-ChildItem $env:TEMP -Force | ForEach-Object {
    $size = if ($_.PSIsContainer) {
        (Get-ChildItem $_.FullName -Recurse -File -Force -ErrorAction SilentlyContinue |
         Measure-Object Length -Sum).Sum
    } else { $_.Length }
    [PSCustomObject]@{ Name = $_.Name; SizeGB = [math]::Round($size/1GB, 2) }
} | Sort-Object SizeGB -Descending | Select-Object -First 5

One folder stood out: wsl-crashes at 77 GB. Inside were sixteen crash dump files, each around 10 GB, with names like wsl-crash-*-_usr_bin_node-6.dmp. WSL had been crashing a Node.js process and writing a 10 GB core dump (a snapshot of memory at the moment of the crash) every single time. Nobody told Windows to delete them, so they just kept stacking up.

Here is the important part: the full disk was a symptom, not the real bug. Something was crashing. Deleting the dumps frees space today, but until the crash itself is fixed they will come back. If the crashing process is tied to a specific Node version, switching it can help, see How to Switch Node.js Version in WSL Ubuntu. Cleanup buys you breathing room to chase the root cause.

Step 3: Clean the safe stuff

Crash dumps are pure garbage, so they are safe to delete. Old temp files and the Recycle Bin are safe too. What you do not delete is the WSL Linux disk or your Docker data, those are real files you still use. Clear the dumps and empty the Recycle Bin:

Get-ChildItem "$env:TEMP\wsl-crashes" -File -Force | Remove-Item -Force
Clear-RecycleBin -Force
  • Get-ChildItem ... | Remove-Item -Force: deletes the crash dump files
  • Clear-RecycleBin -Force: empties the Recycle Bin without a prompt

On the example machine this took free space from 34.5 GB back up to about 123 GB, roughly 89 GB recovered, with no real data touched.

Step 4: Clean Docker (optional)

Docker tends to hoard unused images and build cache. First, see what can be reclaimed (this changes nothing):

docker system df

Then prune. Here are the common options, from safe to aggressive:

CommandWhat it removes
docker system prune -fStopped containers, dangling images, unused networks, build cache
docker system prune -a -fEverything above, plus all unused images (you re-pull later)
docker builder prune -fBuild cache only

A word of caution: never add --volumes unless you are sure, since that deletes data stored in Docker volumes (like local databases). If you only want to clear the leftover untagged images, the focused approach in How to Remove All None Tag on Docker Images is a gentler option. Docker Desktop must be running for any prune command to work.

Step 5: Automate it with a reusable script

A one-time cleanup is a chore you will forget. A small script fixes that. The one below is built to be careful: it can run in preview mode (it shows what it would delete without deleting anything), it logs every action, and it never touches real data. Save it as Clean-CDrive.ps1.

<#
.SYNOPSIS
    Safe C: drive cleanup for recurring WSL crash dumps and temp bloat.
.DESCRIPTION
    Deletes known-safe junk: WSL crash dumps, old temp files, Recycle Bin,
    and optionally prunes Docker. Logs everything. Never touches WSL/Docker
    virtual disks, your files, or running files.
.PARAMETER WhatIf
    Dry run. Shows what WOULD be deleted without deleting anything.
.PARAMETER TempOlderThanDays
    Only delete %TEMP% items older than this many days (default 7).
.PARAMETER MinFreeGB
    Only run cleanup when free space is BELOW this value.
.PARAMETER SkipDocker
    Do not touch Docker at all.
.PARAMETER DockerAll
    Aggressive Docker prune (removes ALL unused images, not just dangling).
#>
[CmdletBinding()]
param(
    [switch]$WhatIf,
    [int]$TempOlderThanDays = 7,
    [double]$MinFreeGB = 0,
    [switch]$SkipDocker,
    [switch]$DockerAll
)

$ErrorActionPreference = 'SilentlyContinue'
$logFile = Join-Path $PSScriptRoot 'Clean-CDrive.log'

function Write-Log {
    param([string]$Msg)
    $line = ('{0}  {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Msg)
    Write-Host $line
    Add-Content -Path $logFile -Value $line
}
function Get-FreeGB { [math]::Round((Get-PSDrive C).Free / 1GB, 1) }

Write-Log '=========================================='
Write-Log ('Clean-CDrive start  (WhatIf={0})' -f $WhatIf.IsPresent)
$freeBefore = Get-FreeGB
Write-Log ('Free space before: {0} GB' -f $freeBefore)

if ($MinFreeGB -gt 0 -and $freeBefore -ge $MinFreeGB) {
    Write-Log ('Free {0} GB is at or above threshold {1} GB. Nothing to do.' -f $freeBefore, $MinFreeGB)
    return
}

$freedTotal = 0

# 1. WSL crash dumps
$wslCrash = Join-Path $env:TEMP 'wsl-crashes'
if (Test-Path $wslCrash) {
    foreach ($f in (Get-ChildItem $wslCrash -File -Force)) {
        if ($WhatIf) {
            Write-Log ('  WOULD DELETE: {0} ({1} GB)' -f $f.Name, [math]::Round($f.Length/1GB,2))
        } else {
            $b = $f.Length; Remove-Item $f.FullName -Force
            if (-not (Test-Path $f.FullName)) { $freedTotal += $b }
        }
    }
}

# 2. Old temp items
$cutoff = (Get-Date).AddDays(-$TempOlderThanDays)
$skip = @('claude','wsl-crashes')
foreach ($i in (Get-ChildItem $env:TEMP -Force |
        Where-Object { $skip -notcontains $_.Name -and $_.LastWriteTime -lt $cutoff })) {
    $sz = if ($i.PSIsContainer) {
        (Get-ChildItem $i.FullName -Recurse -File -Force | Measure-Object Length -Sum).Sum
    } else { $i.Length }
    if ($null -eq $sz) { $sz = 0 }
    if ($WhatIf) {
        Write-Log ('  WOULD DELETE: {0} ({1} GB)' -f $i.Name, [math]::Round($sz/1GB,2))
    } else {
        Remove-Item $i.FullName -Recurse -Force
        if (-not (Test-Path $i.FullName)) { $freedTotal += $sz }
    }
}

# 3. Recycle Bin
if ($WhatIf) { Write-Log '[recyclebin] WOULD empty' }
else { try { Clear-RecycleBin -Force -ErrorAction Stop; Write-Log '[recyclebin] emptied' }
       catch { Write-Log ('[recyclebin] {0}' -f $_.Exception.Message) } }

# 4. Docker prune (never --volumes; that would delete data)
if ($SkipDocker) { Write-Log '[docker] skipped' }
elseif (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
    Write-Log '[docker] not installed. skipped'
} else {
    $ver = & docker version --format '{{.Server.Version}}' 2>$null
    if ($LASTEXITCODE -ne 0 -or -not $ver) {
        Write-Log '[docker] daemon not running. skipped'
    } elseif ($WhatIf) {
        Write-Log '[docker] reclaimable (docker system df):'
        (& docker system df 2>$null) | ForEach-Object { Write-Log ('    ' + $_) }
        Write-Log ('  WOULD PRUNE: docker system prune -f{0}' -f $(if($DockerAll){' -a'}else{''}))
    } else {
        $a = @('system','prune','-f'); if ($DockerAll) { $a += '-a' }
        Write-Log ('[docker] running: docker {0}' -f ($a -join ' '))
        (& docker @a 2>&1) | ForEach-Object { Write-Log ('    ' + $_) }
    }
}

$freeAfter = Get-FreeGB
if ($WhatIf) { Write-Log ('DRY RUN complete. Free: {0} GB (no changes)' -f $freeAfter) }
else { Write-Log ('Freed ~{0} GB. Free: {1} -> {2} GB' -f [math]::Round($freedTotal/1GB,2), $freeBefore, $freeAfter) }
Write-Log 'Clean-CDrive end'
$global:LASTEXITCODE = 0
exit 0

To preview first (nothing gets deleted), then run for real:

.\Clean-CDrive.ps1 -WhatIf
.\Clean-CDrive.ps1
  • -WhatIf: preview mode, lists every “WOULD DELETE” line but changes nothing
  • No flag: the real run, it deletes and prints how much space it freed
  • -DockerAll: also clears all unused Docker images
  • -MinFreeGB 50: only act when free space drops below 50 GB

Step 6: Schedule a weekly preview

The safest habit is a weekly report that deletes nothing. It runs in the background, writes to the log, and lets you decide when an actual cleanup is worth it. This registers a Windows scheduled task that runs every Monday at 9 AM in preview mode. Replace YourName with your Windows username:

$script = "C:\Users\YourName\Scripts\Clean-CDrive.ps1"
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-NoExit -NoProfile -ExecutionPolicy Bypass -File `"$script`" -WhatIf"
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At 9:00AM
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable
Register-ScheduledTask -TaskName "CDrive-WeeklyPreview" `
    -Action $action -Trigger $trigger -Settings $settings -Force
  • New-ScheduledTaskAction: defines what runs (PowerShell with the script in preview mode)
  • -NoExit: keeps the window open after it runs so you can read the report
  • New-ScheduledTaskTrigger -Weekly: sets it to run every Monday morning
  • -StartWhenAvailable: runs late if the PC was off at the scheduled time

When the weekly report shows the dumps creeping back, you run the real cleanup by hand. That keeps a machine that quietly deletes files out of the picture, you stay in control. If high disk use is paired with sluggish performance, it is often the same underlying cause as VSCode WSL2 high memory usage from AI coding extensions, worth checking while you are at it.

Conclusion

A full C: drive is almost never random. By measuring first, you found 77 GB of WSL crash dumps, cleaned them and other safe junk to recover about 89 GB, and put a weekly preview in place so it never surprises you again. The cleanest cleanup is one you rarely need to run, so the real next step is fixing why WSL was crashing. Start with How to Fix WSL CreateVm/HCS/ERROR_FILE_NOT_FOUND on Windows if WSL is also unstable, or tune your setup with How to Switch Node.js Version in WSL Ubuntu if a Node process is the one crashing.