Why is the GitHub Actions Windows runner so slow, and how do I speed it up?
A large share of a slow Windows job is often time spent on many small files. Checkout, dependency restore and cache extraction can each handle tens of thousands, and reports on GitHub put much of the cost in file handling and the cache’s tar step. Time every step first, then cut file counts and cache fewer, larger archives. Start-up adds more.
The GitHub facts in this post were read from GitHub’s own documentation, issues and changelog on 10 November 2026, and each is dated where the date matters. Several of the threads are bug reports, not GitHub stating a cause, and the text says which. Where something is ours, it says so.
Where does a Windows job spend its time: queue, machine start, checkout, cache restore or the build itself?
In all five, and the run page shows only some of them, so time each one before you change anything. The wait for a runner and the machine starting are one gap, which is the time between the job being created and its first step starting. Checkout, the dependency or cache restore, the build and the cache save at the end are steps, and each has a duration. GitHub’s API holds both. This prints how long one job waited and how long each of its steps took, in seconds; replace the owner, repository and run number.
Read it as a budget. If checkout and restore together outlast the build, the file-count advice below is where to look. If the gap before the first step is the biggest number, no workflow edit shortens it.
On runners.io a virtual machine is created for each Windows job, so start-up is part of the total, and we publish the measurement. Across 155 completed Windows jobs from 21 October to 7 November 2026, the median time from queued to started was 3 min 12 s, the middle half ran from 2 min 30 s to 5 min 32 s, and the extremes were 58 s and 24 min 55 s. Most of those jobs were our own tests, some run in batches, so the long waits include jobs queuing behind each other; the ten with no other Windows job queued within fifteen minutes either side started in 2 min to 5 min 55 s, median 3 min 13 s. The Windows runners page keeps the current figures.
We have not published per-step timings for checkout or cache restore on our Windows runners, so trust your own runs for those. And waiting for a machine is not billed, so the start-up gap costs you time rather than minutes.
Why is actions/cache so much slower on Windows than on Linux?
The reports on GitHub put the time in unpacking the archive, not in downloading it. In the long-running thread on actions/cache, a reporter found retrieval times similar across platforms and the wait in the un-archiving step, and another timed the tar operation as the culprit. The numbers below are what reporters measured on the dates shown; none is ours.
| Report | Date | Quicker side | Slower side |
|---|---|---|---|
| actions/cache #752, Adnn | 2 Mar 2022 | Ubuntu 20.04: about 10 s | Windows 2019: often about 5 min (a cache of about 300 MB) |
| actions/cache #752, mattjohnsonpint | 6 May 2022 | Ubuntu: 18 s | Windows: 4 min 5 s |
| actions/cache #752, wrexbe | 23 Jun 2022 | Ubuntu: 16 s | Windows: 2 min 29 s |
| setup-node #975, charlespwd | 27 Feb 2024 | Ubuntu: about 5 s | Windows: about 4 min (yarn cache) |
| actions/cache #752, zeitlinger | 17 Apr 2026 | Windows with bsdtar: 16 to 19 s to extract | Windows with the default tar: about 80 s to extract (windows-2025, 568 MB, about 40,000 files) |
Two explanations recur. A contributor who measured a 568 MB, roughly 40,000-file cache in April 2026 blamed the MSYS tar that ships with Git for Windows: it adds a cost per file and starts an external zstd for each stream, which hurts most when there are many small files. A maintainer in the setup-node thread, in 2024, named NTFS handling many small files less efficiently than ext4, with slower disk and network I/O and background Windows processes. Neither is GitHub saying the cause is settled.
It also moves. The setup-node thread was closed in January 2026 after a maintainer said on 11 December 2025 that the slowness was no longer reproducing, and a thread reporting 15 to 20 second Windows checkouts against 2 to 3 seconds on Ubuntu was closed in March 2025 as beyond the scope of runner images, with no cause given. Take today’s timings, not an old issue’s.
On runners.io the cache is ours, and a Windows job reaches it with no workflow change. The one difference from Linux is that the runner is a .NET program that reads the machine certificate store and ignores NODE_EXTRA_CA_CERTS, so the job’s certificate authority is added to the machine store; if that cannot be set up, the job falls back to GitHub’s cache. We have not measured Windows restore times against GitHub’s cache and do not claim a difference. On the evidence above, the unpacking runs on the job’s own machine whoever serves the archive, so a quicker download does not remove it. The cache documentation and how the cache works on our runners have the route; the limits are in the cache limit guide.
Which settings help (fewer files, compression, the D: drive, Defender exclusions where allowed)?
Fewer files helps most. Compression, the choice of drive and Defender exclusions help less, or only on some images, so test each against your own step timings.
Fewer files. Checkout already fetches one commit and no LFS files by default. Sparse checkout writes only the directories you list, plus the files in the repository root. Cache what is stored as few large files, such as a package manager’s download cache, rather than an unpacked tree such as node_modules, and compare a cached run with an uncached one: in the setup-node thread the two were nearly equal on Windows, which means the cache was not earning its keep. The workflow below shows both; it is untested on runners.io as written, so check the checkout, the file count and the cache save and restore in your own run.
Compression. The cache action chooses the archive tool for the runner’s OS, and there is no setting for it today. A 7z archive extracted in about 41 seconds in the thread but was 28 per cent larger, and two reporters saw no gain from putting GNU tar first on the path. A pull request to let the cache prefer Windows’s built-in bsdtar, reported at about four times faster to extract, was still open on 10 November 2026, so there is nothing to switch on yet.
The D: drive. On GitHub-hosted runners the second volume has been worth using: a maintainer said in 2023 there was no simple fix to bring C: up to D:’s performance. GitHub announced that D: would be removed from Windows Server 2025 runners for public repositories from 14 July 2025, then that it would return from 18 August 2025 with the default working directory moved there. Our image documentation describes one 100 GB boot disk shared with the operating system and the tools, with about 30 GB free when a job starts on Windows Server 2025, and does not describe a D: drive. List the volumes before you move anything.
Defender exclusions. The evidence is weak. In the runner-images thread two people turned real-time monitoring off and saw almost no change, and the cache contributor put Defender at about 12 seconds on the default tar path. Our jobs run as runneradmin, a local administrator, so an exclusion may be possible, but we have not published what Defender does on the image. These steps read the volumes and the scanner’s state, and add an exclusion only if the machine allows it. We have not run them on our runners, so what they print is for you to read, not for us to promise.
Can I run Docker and Windows containers on a Windows runner?
Yes, Windows containers, and only those. GitHub’s documentation says that job containers, service containers and Docker container actions need a Linux runner, so container: and services: do not start on a Windows job. What does work is calling Docker yourself against Windows images. Our Windows images carry Docker Engine 29.7.2 for Windows containers, running when the job starts, with Compose 2.40.3, and the image documentation lists what else is installed.
Match the base image to the host. Microsoft’s compatibility table says a Windows Server 2025 host runs 2025 and 2022 base images with process isolation, and 2019 and 2016 images only with Hyper-V isolation, which our images do not offer. On Windows Server 2022, use a 2022 base image. Expect the first pull to cost time: our 2022 image does not pre-pull the five container images GitHub’s does, about 16 GB, and there is no Docker layer cache on Windows, so actions/cache is the cache a Windows job has.
Keep the first step as a guard. A runner-images issue opened in February 2026 reports Docker sometimes not running when a GitHub-hosted Windows job starts; the check costs a second and turns a pipe error into a clear failure. We ran all three steps on 10 November 2026 on runnersio-4vcpu-windows-2025 and runnersio-4vcpu-windows-2022. On both, docker version reported windows and docker info reported process isolation, and docker run of the servercore image that matches the host exited 0, with ver printing the host’s own build: 10.0.26100.33822 for the ltsc2025 image on Windows Server 2025, and 10.0.20348.5870 for ltsc2022 on Server 2022. The image was not on the machine, and the first pull took 160 seconds on 2025 and 134 seconds on 2022.
Which Windows Server versions are available (2022, 2025), and what changed with windows-latest?
GitHub offers Windows Server 2022 as windows-2022 and Windows Server 2025 as windows-2025, and windows-latest points at 2025. That label moved from Server 2022 to Server 2025 in a rollout GitHub scheduled from 2 to 30 September 2025, and the Windows Server 2019 image was scheduled to be fully retired by 30 June 2025.
The 2026 change is the one to check. GitHub’s May 2026 notice said windows-latest and windows-2025 would move to Visual Studio 2026 by default, over a week beginning 8 June and complete by 15 June 2026. The runner-images issue for the change lists Visual Studio going from 17.14 to 18.5, CMake from 3.31.6 to 4.3.2, and 13 Visual Studio components removed. To stay on Visual Studio 2022, GitHub points to windows-2022. The version a job actually got is printed in its Set up job log, which is the thing to read when a build changes under you.
On runners.io the two labels are runnersio-4vcpu-windows-2025 and runnersio-4vcpu-windows-2022, in 2, 4, 8 and 16 vCPU sizes, and the labels page lists them all. Both are Server Core, with no desktop and no Edge, built from GitHub’s own runner-images scripts. The 2025 image carries the Visual Studio 2026 Build Tools (18.11) and the 2022 image the 2022 ones (17.14), without the IDE, so a job that needs the full Visual Studio image stays on GitHub. We do not sell windows-2025-vs2026 or the Arm labels.
Moving a job is a change to its runs-on line, and changing it back returns it to GitHub. The pricing page has each size’s rate, and the jobs page shows where a job’s time goes.