Make WSL Start Fast: Stop Slow Shells and Broken Init Scripts

Was this helpful?

WSL can feel like a superpower: Linux tooling with a Windows desktop, instant context-switching, no reboot tax. Then one day you open your distro and… you wait. And wait. A blank terminal stares back like it’s negotiating its contract.

Most “WSL is slow” complaints aren’t about CPU. They’re about timeouts, shell init scripts that do too much, broken mounts, and networking assumptions that were true on a laptop in 2019 and false on a corporate VPN in 2026. The fix is rarely one magic tweak. It’s disciplined diagnosis and ruthless trimming.

Interesting facts and context

  • WSL 1 vs WSL 2 is a different universe. WSL 1 translates Linux syscalls into Windows calls; WSL 2 runs a real Linux kernel in a lightweight VM. Startup failure modes change accordingly.
  • WSL 2 uses a virtual disk (VHDX) per distro. That means storage performance is shaped by the VHDX, host filesystem behavior, antivirus scanning, and where the VHDX lives (e.g., slow HDD vs NVMe).
  • Systemd support arrived late and changed expectations. For years, people hacked around “no init” with custom scripts. When systemd became supported, those hacks didn’t magically retire.
  • /mnt/c is not “just a folder.” It’s a cross-OS filesystem boundary with different semantics and overhead. Heavy builds there can feel like running in mud.
  • Corporate VPNs love breaking name resolution. A WSL shell that does one DNS lookup per prompt can go from “fine” to “hangs for 8 seconds” overnight.
  • Many WSL slow-start issues are self-inflicted. Prompt frameworks, plugin managers, and “helpful” scripts that call git, kubectl, or cloud CLIs at startup are the usual culprits.
  • WSL startup isn’t one thing. There’s distro initialization, mount setup, networking setup, then your shell startup, then whatever your shell scripts decide to do.
  • Windows Defender can matter more than your CPU. Real-time scanning on repo directories (especially node_modules and build artifacts) can dominate perceived latency.
  • PATH can be a performance problem. Importing a huge Windows PATH into WSL makes command lookups slower and breaks tooling in creative ways.

How WSL startup actually works (so you stop guessing)

When you launch a WSL distro, you’re not “booting Linux” the way you boot a VM in a hypervisor UI. But you also aren’t merely launching a process the way WSL 1 used to feel. WSL 2 starts (or resumes) a lightweight VM hosting a Linux kernel, attaches the distro’s VHDX, configures virtual networking, and then runs your requested command—usually your default shell.

From a latency perspective there are four layers, and each has its own signature when it goes wrong:

1) VM bring-up / resume

If WSL has been idle, it may have shut down. Your next launch has to bring up the WSL VM. This is usually fast, but it can drag when the host is under memory pressure, when storage is slow, or when something forces extra work (like aggressive endpoint protection).

2) Distro initialization (and systemd, if enabled)

Modern WSL can run systemd. That’s good—services behave normally—but it adds boot-like behavior. If a unit is slow, fails, or waits on network, you’ll pay that cost during “startup.” If systemd isn’t enabled, you may still have legacy init scripts or login-time hacks that attempt to emulate it.

3) Mounts and interop wiring

WSL wires up Windows drives, sets up /mnt, and optionally imports Windows environment variables like PATH. Mount behavior is configurable in /etc/wsl.conf. When mounts are wrong, you’ll see hangs that look like “shell won’t open,” but the real offender is a blocking mount or a filesystem probe.

4) Shell initialization

This is the part you own. Bash reads /etc/profile and your personal dotfiles. Zsh reads different ones. Fish has its own world. If your prompt runs git status on a massive repo, or your profile calls a cloud CLI that waits on a proxy, the terminal will look “stuck” even though WSL is fine.

One quote worth keeping on the wall, because startup performance is mostly waiting on dependencies:

paraphrased idea — Werner Vogels: “Everything fails, all the time; design systems that expect it.”

In WSL-land, “failure” often means “timeout,” which is failure wearing a suit.

Fast diagnosis playbook (first/second/third)

If you want WSL to start fast, you need to identify which layer is slow. Don’t start by rewriting dotfiles. Don’t start by reinstalling the distro. Start by measuring in a way that isolates the bottleneck.

First: isolate shell init vs everything else

  • Launch WSL with a command that avoids your interactive shell init.
  • If that’s fast, your problem is in dotfiles/prompt/plugins.
  • If that’s still slow, your problem is WSL/distro/systemd/mount/network.

Second: check systemd and services (if enabled)

  • Ask systemd how long it took.
  • Look for units waiting on network, DNS, or mounts.
  • Disable anything not needed for dev shells.

Third: network/DNS and mounts

  • DNS timeouts are the #1 “random 5–10 seconds” culprit.
  • /mnt/c access and Windows PATH import are common “death by a thousand cuts” culprits.
  • If everything else looks clean, then go after storage/VHDX placement and antivirus exclusions.

Joke #1: If your prompt runs three network calls, it’s not a prompt. It’s a distributed system with feelings.

Practical tasks: commands, outputs, and decisions

You’ll fix this faster if you treat it like an incident: gather evidence, isolate variables, make one change at a time, and keep a rollback path. Below are real tasks with commands, example outputs, and what you decide from them.

Task 1: Measure the time to start WSL without your interactive shell

cr0x@server:~$ /usr/bin/time -p wsl.exe -d Ubuntu-22.04 -- bash -lc 'true'
real 0.62
user 0.00
sys 0.00

What the output means: The real number is wall-clock time. This bypasses your interactive shell prompt logic and most dotfile drama.

Decision: If this is fast (<1–2s) but your normal terminal is slow, go straight to shell initialization debugging. If this is slow, keep digging into systemd/mounts/network/storage.

Task 2: Compare interactive shell startup time

cr0x@server:~$ /usr/bin/time -p bash -lic 'exit'
real 2.87
user 0.19
sys 0.08

What the output means: This measures login + interactive startup. If real is much larger than user and sys, you’re waiting on I/O, network, or blocking subprocesses.

Decision: If interactive is slow but non-interactive is fast, dotfiles/prompt/plugins are your primary suspect.

Task 3: See what shell you’re actually running and how it’s invoked

cr0x@server:~$ echo "$0"; ps -p $$ -o pid,ppid,cmd
-bash
  PID  PPID CMD
  812   799 -bash

What the output means: A leading dash indicates a login shell. That changes which files are sourced (/etc/profile, ~/.bash_profile, etc.).

Decision: If you’re accidentally running a login shell when you don’t need it, fix your terminal profile (Windows Terminal / VS Code) to start a non-login shell for speed.

Task 4: Trace what your shell is sourcing (bash)

cr0x@server:~$ bash -lic 'set -o posix; shopt -po login_shell; echo "BASH_VERSION=$BASH_VERSION"; exit'
set -o posix
shopt -s login_shell
BASH_VERSION=5.1.16(1)-release

What the output means: Confirms you’re in a login shell and shows bash version (relevant for startup features and compatibility).

Decision: If login shells are slow, move heavy stuff out of login-only files and into on-demand functions, or stop using login shells for interactive sessions.

Task 5: Use bash xtrace to find the slow line

cr0x@server:~$ bash -lic 'PS4="+\t\D{%s}\t"; set -x; source ~/.bashrc; exit' 2> /tmp/bashrc.trace
...output...

What the output means: The trace file has timestamps per executed line. Look for big gaps between timestamps.

Decision: Remove, defer, or guard the slow commands. If the gap coincides with calling git, kubectl, gcloud, npm, or DNS tools, you’ve found your anchor.

Task 6: Zsh profiling (if you use zsh)

cr0x@server:~$ zsh -lic 'zmodload zsh/zprof; source ~/.zshrc; zprof' | head -n 12
num  calls                time                       self            name
-----------------------------------------------------------------------------------
 1)    1         189.22    189.22   38.51%    189.22    189.22   compinit
 2)    1         121.77    121.77   24.78%    121.77    121.77   gitstatus_init

What the output means: It ranks functions by time. compinit and fancy git prompt helpers often dominate.

Decision: Cache completion, reduce plugins, or switch to a simpler prompt. In WSL, “snappy” beats “cute.”

Task 7: Check whether systemd is enabled and if it’s slow

cr0x@server:~$ ps -p 1 -o pid,comm,args
  PID COMMAND         COMMAND
    1 systemd         /sbin/init

What the output means: If PID 1 is systemd, then WSL startup includes systemd unit work.

Decision: If systemd is enabled and you don’t need services, consider disabling it. If you do need it, measure and tune the units.

Task 8: Measure systemd boot time and find slow units

cr0x@server:~$ systemd-analyze time
Startup finished in 2.103s (kernel) + 5.882s (userspace) = 7.985s
graphical.target reached after 5.841s in userspace

What the output means: Userspace time is where slow services live.

Decision: If userspace is multiple seconds, list blame.

cr0x@server:~$ systemd-analyze blame | head -n 10
3.214s systemd-networkd-wait-online.service
1.102s snapd.service
0.688s apt-daily.service
0.512s systemd-resolved.service

What the output means: These units consumed the most time. wait-online is the classic “why am I waiting for a network that doesn’t exist?” unit.

Decision: Mask or reconfigure services that don’t belong in a dev shell environment.

Task 9: Check mounts and whether you’re paying extra for Windows drives

cr0x@server:~$ mount | grep -E '/mnt/|drvfs' | head -n 5
C: on /mnt/c type drvfs (rw,noatime,uid=1000,gid=1000,metadata,case=off)
D: on /mnt/d type drvfs (rw,noatime,uid=1000,gid=1000,metadata,case=off)

What the output means: Windows drives are mounted as drvfs. Options like metadata and case affect compatibility and performance.

Decision: If your workflow uses Linux permissions heavily, keep metadata. If not, you can simplify. But the bigger win is moving hot repos to the Linux filesystem.

Task 10: Detect if you’re working in /mnt/c (performance trap)

cr0x@server:~$ pwd
/mnt/c/Users/alex/source/mega-repo

What the output means: You’re on Windows storage. Builds, package installs, and git operations will often be slower.

Decision: Clone the repo under ~ (Linux filesystem) and use an editor that can access it (VS Code Remote – WSL is designed for this).

Task 11: Check DNS behavior (fast fail vs slow hang)

cr0x@server:~$ getent hosts github.com
140.82.121.4   github.com

What the output means: DNS resolution succeeded quickly. If it hangs, that’s your smoking gun.

Decision: If slow/hanging, inspect /etc/resolv.conf and WSL DNS settings. If it’s fast, your slowdown is elsewhere.

Task 12: Confirm whether /etc/resolv.conf is auto-generated and what nameserver you’re using

cr0x@server:~$ ls -l /etc/resolv.conf; head -n 10 /etc/resolv.conf
-rw-r--r-- 1 root root 235 Jan 12 09:14 /etc/resolv.conf
# This file was automatically generated by WSL. To stop automatic generation...
nameserver 172.24.64.1
search corp.example

What the output means: WSL is generating resolv.conf and pointing to a virtual gateway IP. On VPNs, this can be fine or it can be a timeout party.

Decision: If DNS is flaky/slow, disable auto-generation and set a stable resolver strategy for your environment (details in the networking section).

Task 13: Check if Windows PATH import is bloating command lookup

cr0x@server:~$ echo "$PATH" | tr ':' '\n' | wc -l
142

What the output means: 142 PATH entries is not a flex. It’s a liability. Many are Windows paths that cause extra filesystem checks.

Decision: Consider disabling appendWindowsPath in /etc/wsl.conf and explicitly adding only what you need.

Task 14: Identify whether your prompt is doing expensive git work

cr0x@server:~$ PS1='\u@\h:\w\$ ' bash --noprofile --norc
cr0x@server:~$ exit
exit

What the output means: This launches a clean bash with a simple prompt. If it’s instantly responsive, your fancy prompt is the problem.

Decision: Replace prompt frameworks or configure them to avoid calling git on every prompt repaint (or only do it inside git repos, with caching).

Task 15: Quick check for hanging commands in dotfiles by running them with a timeout

cr0x@server:~$ timeout 2s bash -lc 'source ~/.bashrc; echo ok' || echo "bashrc exceeded 2s"
bashrc exceeded 2s

What the output means: Your bashrc can’t finish within two seconds in a non-interactive run. Something inside is blocking.

Decision: Use the tracing approach (Task 5) and delete/guard the slow bits.

Task 16: Check whether Windows interop is on and whether it’s hurting you

cr0x@server:~$ cat /proc/sys/fs/binfmt_misc/WSLInterop 2>/dev/null | head -n 5
enabled
interpreter /init
flags: P
offset 0
magic 4d5a

What the output means: Interop is enabled, so running Windows executables from Linux works. That’s convenient—and sometimes slow when scripts accidentally invoke Windows tools.

Decision: If you see slow commands and discover they resolve to Windows binaries (like git.exe), fix PATH order or disable interop for that workflow.

Stop slow shells: profile scripts, prompt frameworks, and PATH traps

Shell startup is where performance goes to die because it’s “just a script.” Scripts grow like mold: slowly, then suddenly you’re sourcing five plugin managers and a theme engine to print your username in italics.

WSL amplifies this because a lot of shell sugar assumes low-latency local resources. But in WSL you might be traversing /mnt/c, invoking Windows binaries by accident, or triggering DNS lookups through a VPN tunnel that’s half-asleep.

Know what files run, and stop double-sourcing

Bash commonly sources:

  • /etc/profile (login shells)
  • ~/.bash_profile or ~/.profile (login shells)
  • ~/.bashrc (interactive shells)

A classic slowdown is ~/.bash_profile sourcing ~/.profile sourcing ~/.bashrc and then the terminal launches a login interactive shell that also sources ~/.bashrc. You just ran your slow code twice. Nobody notices until the repo grows and git status becomes expensive.

Prompt frameworks: pick one, configure it like you mean it

Starship, oh-my-zsh themes, powerlevel10k, custom git prompt helpers—these tools are great. They’re also perfectly happy to run external commands repeatedly. In WSL, external commands can cross boundaries and call Windows executables, or hit filesystem layers that don’t cache the way you expect.

Rules I enforce on teams:

  • No network calls during prompt render. That includes cloud CLIs, kube context fetchers that hit APIs, or “helpful” version checks.
  • No recursive git status on huge repos by default. Prefer quick checks or cached status; avoid git status in the prompt if you can’t bound its cost.
  • Defer heavy initialization. Completion systems, language version managers, and direnv-like tools should load lazily when possible.

PATH: keep it short, deterministic, and Linux-first

WSL can import Windows PATH entries. That sounds helpful until your shell has to check 140 directories to resolve python, and half of those directories are on /mnt/c. Worse: sometimes you run git.exe when you think you’re running Linux git. Now your repo operations bounce across the boundary.

Practical guidance:

  • Disable Windows PATH append unless you truly need it.
  • If you need a couple Windows tools, add them explicitly and put them last.
  • When debugging “slow commands,” check which binary is used with type -a or command -v.

Guard any optional tooling with fast checks

If your shell init runs a tool that may not exist, don’t call it blindly and wait for failure. Check for it first:

  • command -v tool >/dev/null is cheap.
  • Calling tool --version is not always cheap (some CLIs initialize plugins and config).

Also stop doing “auto update checks” on shell startup. If you really need that, schedule it in the background after the prompt appears.

Broken init scripts and systemd: when “boot” isn’t boot

WSL historically trained people to treat distro startup like “run a shell and hope for the best.” Then systemd support arrived and everyone started enabling services. That’s fine—until it isn’t.

Decide whether you actually need systemd

If your workflow needs Docker inside WSL, background daemons, timers, or service supervision, systemd can be the correct solution. If you mostly run compilers and CLI tools, systemd is often optional overhead.

Even with systemd enabled, you should be picky. WSL is not a pet server. It’s a dev environment that should start instantly and be disposable.

Common slow units in WSL

  • systemd-networkd-wait-online.service: waits for network readiness; often useless in WSL where the network comes up differently.
  • snapd.service: can add noticeable startup latency; snaps are not always worth it in WSL.
  • apt-daily.service and timers: background updates can spike disk usage right when you open a shell.
  • resolved integration: can interact poorly with WSL’s generated resolv.conf depending on configuration.

Masking vs disabling: pick the right hammer

If a unit is actively harmful at startup, mask it so it cannot start accidentally. If you just don’t want it enabled by default, disable it. For WSL startup performance, masking “wait-online” is a common win.

And yes: sometimes a “broken init script” is your own ~/.profile pretending to be an init system. If you’ve got scripts starting daemons, exporting environment, and touching mounts, you built a mini-init. It has all the disadvantages of systemd and none of the diagnostics.

Mounts and file I/O: the /mnt/c tax, automount options, and where your repo lives

Most WSL performance horror stories involve the Windows filesystem. Not because it’s bad, but because it’s different. Linux tools assume POSIX semantics. Windows semantics are not POSIX semantics. WSL papers over the difference. Paper is not free.

Put hot code on the Linux filesystem

If you care about startup and interactive responsiveness, your main repos and build outputs should live under /home inside the distro. That means cloning in WSL, not editing a clone on C:\. You can still edit from Windows using WSL-aware tooling.

This one change fixes:

  • Slow git status, git diff
  • Slow dependency installs (npm/pip/gradle caches)
  • Weird file permission behavior
  • Endpoint scanning overhead (often less painful inside the VHDX)

Tune automount, but don’t cargo-cult it

/etc/wsl.conf can control automount behavior. People love to paste a config from a blog post and then forget they did it. Six months later, something breaks and everyone blames WSL.

Key ideas:

  • metadata enables POSIX permissions on Windows files. Useful, but can add overhead and confusion if you don’t need it.
  • case behavior matters for toolchains and repos with case-sensitive paths.
  • appendWindowsPath can bloat PATH and slow command lookup.

Be intentional about what you access across the boundary

Accessing a couple config files on /mnt/c is fine. Running a build that does millions of tiny file operations there is a performance self-own. If your prompt framework scans directories on /mnt/c at startup, that’s a slow shell by design.

Networking and DNS: the silent timeout factory

DNS issues are the most common cause of “WSL hangs for 5–10 seconds sometimes.” The reason is boring: many tools do name resolution on startup, and they often do it synchronously. Your shell doesn’t look slow; it looks frozen.

Where the DNS pain comes from in WSL:

  • WSL auto-generates /etc/resolv.conf based on Windows networking.
  • Corporate VPNs modify Windows DNS and routing on the fly.
  • Some orgs push DNS servers that are reachable only when connected to VPN.
  • Tools like git, kubectl plugins, language package managers, and prompt scripts may trigger DNS.

Detect it quickly

If getent hosts some-domain hangs, you’ve found a primary offender. Then you decide whether to fix DNS at the WSL layer (resolv.conf behavior) or at the dotfile/tooling layer (stop tools from doing DNS during startup).

Make DNS behavior deterministic

For many corporate environments, the most stable setup is:

  • Disable auto-generation of /etc/resolv.conf.
  • Write a resolv.conf that works both on and off VPN, or generate it via a script tied to VPN state.
  • Avoid relying on “whatever Windows thinks today.”

But be careful: if you hardcode public resolvers in a locked-down org, internal domains will break. The correct approach is environment-specific. Reliability engineering is the art of respecting reality, not arguing with it.

Storage/VHDX reality: what “disk is slow” means in WSL2

When WSL2 feels slow, people blame “the VM.” When it feels really slow, they blame “Windows.” In practice it’s more specific: it’s your virtual disk, its location, and the host’s storage pipeline.

Understand the VHDX implications

Your Linux filesystem lives in a VHDX file. Random I/O patterns, small file churn, and metadata-heavy workloads (hello, node_modules) are sensitive to host storage performance. If your Windows user profile lives on an encrypted or redirected drive, your WSL VHDX may be living somewhere unpleasant.

Endpoint protection is a performance variable

Real-time scanning can make file-heavy operations crawl. If startup scripts touch lots of files (prompt scanning, completion cache rebuilds, language managers), you can feel it during “WSL startup” even though the real work is file scanning.

Work with security rather than fighting it. The boring, correct practice is to establish sanctioned exclusions for the WSL VHDX location or specific directories that produce high churn. If your org won’t allow exclusions, then your best lever is minimizing file churn on startup and keeping repos inside the Linux filesystem to avoid drvfs overhead.

Joke #2: Antivirus is like a smoke detector that also critiques your cooking technique—useful, but it will absolutely ruin a quiet evening.

Three corporate mini-stories (what actually breaks in real orgs)

Incident caused by a wrong assumption: “DNS is local, so it’s fast”

A platform team rolled out a standardized WSL dev environment for engineers: Ubuntu, zsh, a shiny prompt, a few helper scripts, and kubectl context in the prompt because “it’s helpful.” It tested well in the office. At home, half the team complained that WSL “randomly hangs” when opening a new terminal.

The wrong assumption was subtle: they assumed DNS resolution cost is negligible. On corporate Wi‑Fi, it was. On home networks plus split-tunnel VPN, DNS requests for internal domains timed out before falling back. The prompt script didn’t just read a config file; it invoked kubectl, which invoked a plugin, which tried to resolve an internal API hostname. Every new shell paid the timeout penalty.

The debugging was classic SRE work: reproduce with a clean prompt, then add components back. getent hosts hung exactly when the prompt hung. They fixed it in two ways: (1) removed kubectl calls from prompt render; (2) made DNS deterministic by disabling auto resolv.conf generation and managing resolvers per VPN state.

The lesson stuck: startup paths must be free of network dependency. If you need dynamic context, compute it asynchronously after the prompt appears and cache it with a TTL.

Optimization that backfired: “Let’s put repos on C: for easy access”

A dev productivity group wanted a simple rule: keep code in C:\src so Windows tools and WSL tools can both see it. Everyone loved the convenience. It worked fine for small services.

Then a monorepo arrived with heavy TypeScript builds and massive dependency trees. Suddenly the same laptops that were “fast enough” started burning minutes on installs, and shells took several seconds to display because prompts and completion systems were crawling the repo to infer context.

They tried to optimize drvfs mount options, disabled metadata, added noatime, and tweaked everything they could find. Marginal gains. The fundamental mismatch remained: Linux toolchains doing millions of file operations on a Windows filesystem bridge.

The real fix was boring and slightly inconvenient: put the repo under ~/src inside the Linux filesystem and use WSL-aware editors. They kept a small set of Windows-visible files on /mnt/c when needed, but the hot path moved to ext4 inside the VHDX. Startup and build times snapped back to reasonable.

Boring but correct practice that saved the day: “Profile scripts are production code”

A regulated enterprise had a lot of engineers on WSL, and they treated dotfiles like a personal art project. Then a security hardening update landed: new proxy settings, updated certificate bundles, and a change in internal DNS routing. The next morning, a chunk of the org couldn’t open WSL terminals quickly. Some couldn’t open them at all.

One team didn’t panic because they had a practice that sounds unsexy: they kept shell init logic minimal, version-controlled, and reviewed. Their dotfiles were split into “startup-critical” and “nice-to-have,” with timeouts around anything that could block. They also had a documented escape hatch: start with --noprofile --norc to recover a broken shell.

While other teams were reinstalling distros and blaming Windows updates, this team had a working terminal, could run diagnostics, and could help others. The fix for the wider org ended up being straightforward DNS/proxy alignment, but the teams with disciplined dotfiles lost less time and generated cleaner incident data.

The moral: the stuff that runs at startup is part of your platform. Treat it with the same respect you give production init scripts—because it’s the thing you need when everything else is on fire.

Common mistakes: symptom → root cause → fix

1) Symptom: Terminal opens, stays blank for 5–15 seconds, then prompt appears

Root cause: A blocking command in shell init (often DNS-related) or a prompt framework running slow subprocesses.

Fix: Start a clean shell (bash --noprofile --norc), confirm it’s instant, then trace your init files (bash xtrace or zsh zprof). Remove network calls from prompt render. Add timeouts around optional helpers.

2) Symptom: Only slow when on VPN (or only slow when off VPN)

Root cause: DNS resolvers in /etc/resolv.conf are only reachable in one state; lookups time out.

Fix: Disable auto-generated resolv.conf and manage resolvers intentionally for your environment, or fix Windows-side DNS/VPN configuration so WSL inherits correct behavior.

3) Symptom: cd into a repo is fine, but every new prompt takes 1–2 seconds

Root cause: Prompt runs git status or equivalent on every render; repo is large or on drvfs.

Fix: Simplify prompt or enable caching; avoid scanning on each prompt; move repo to Linux filesystem.

4) Symptom: WSL startup slow after enabling systemd

Root cause: Slow systemd units (network wait-online, snapd, apt timers) dragging userspace startup.

Fix: Use systemd-analyze blame and mask/disable unnecessary units. If you don’t need systemd, disable it.

5) Symptom: Commands feel “laggy,” even simple ones like python or node

Root cause: PATH is huge; command resolution checks many Windows directories; or you’re invoking Windows binaries inadvertently.

Fix: Reduce PATH, disable Windows PATH append, check type -a for binaries, fix PATH order.

6) Symptom: Only slow in one distro; other distros are fast

Root cause: Distro-specific dotfiles, systemd units, or corrupted caches (completion caches, language manager shims).

Fix: Compare systemd blame, compare dotfiles, temporarily rename ~/.bashrc/~/.zshrc, rebuild caches.

7) Symptom: Startup time worsened after “tuning” /etc/wsl.conf

Root cause: Cargo-culted automount/interop settings causing extra work or compatibility fallbacks.

Fix: Revert to minimal config, then reapply changes one by one while measuring. Keep changes that move the needle; delete the rest.

8) Symptom: Random pauses during shell startup, more common on large repos

Root cause: Endpoint protection scanning file-heavy operations triggered by init scripts (completion rebuilds, prompt scans).

Fix: Reduce file churn at startup; consider sanctioned exclusions; keep repos in Linux filesystem; avoid rebuilding caches every launch.

Checklists / step-by-step plan

Step-by-step: get WSL startup under 1 second (or as close as reality allows)

  1. Measure baseline without interactive init. Use Task 1. If that’s slow, don’t touch dotfiles yet.
  2. Measure interactive shell time. Use Task 2. If interactive is the delta, focus on shell init.
  3. Open a clean shell. Use Task 14 (--noprofile --norc) to prove the environment can be fast.
  4. Profile your shell init. Bash xtrace (Task 5) or zsh zprof (Task 6).
  5. Remove network dependencies from startup. No DNS, no cloud CLIs, no kube calls in prompt render.
  6. Move hot repos off /mnt/c. If you’re building on drvfs, you’re paying extra by design.
  7. Trim PATH. Reduce entries, disable Windows PATH import if you can.
  8. Check systemd only if relevant. If PID 1 is systemd, run systemd-analyze blame and mask slow units you don’t need.
  9. Validate DNS speed. getent hosts should be instant. If not, fix resolv.conf strategy.
  10. Re-measure after each change. Don’t stack tweaks and hope; measure, keep, repeat.

Operational checklist: keep it fast over time

  • Dotfiles are reviewed code, not folklore.
  • Prompt must not execute unbounded operations (network, deep filesystem scans).
  • Any optional helper command has a timeout or lazy-load mechanism.
  • Repos live in Linux filesystem by default; drvfs is for occasional sharing, not heavy builds.
  • When enabling systemd, audit and minimize enabled units.
  • On corporate networks, decide a DNS strategy explicitly; don’t rely on “auto.”

FAQ

1) Why is WSL fast sometimes and painfully slow other times?

Because you’re often waiting on timeouts: DNS, network readiness, or a command in your shell init that blocks under certain conditions (VPN state, proxy, unreachable domain).

2) How do I tell if the problem is WSL itself or my shell configuration?

Run WSL with a trivial command (Task 1). If that’s fast but an interactive shell is slow (Task 2), your dotfiles/prompt/plugins are the issue.

3) Is enabling systemd in WSL a bad idea?

Not inherently. It’s correct if you need managed services. It’s also a source of startup latency if you enable units that wait on network or run background maintenance. Measure with systemd-analyze and prune.

4) Why does working under /mnt/c make everything slower?

Because drvfs bridges two OS filesystem models. Linux tools do lots of metadata operations that are more expensive across that boundary. Put build-heavy repos under ~ inside the distro.

5) What’s the quickest dotfile fix that usually helps?

Stop running expensive commands in the prompt. Remove git status from PS1 or configure caching. If you must show git info, keep it lightweight and bounded.

6) How do I know if DNS is the culprit?

If getent hosts some-domain hangs or takes seconds, DNS is involved. Also check whether slowdown correlates with VPN on/off.

7) Should I disable Windows PATH integration?

If your PATH is huge or you keep accidentally running Windows binaries, yes. Disable it and add specific Windows paths only when needed, preferably at the end.

8) I use VS Code with WSL. Does any of this change?

No, it becomes more important. VS Code will spawn shells and run helpers. A slow shell init makes the editor feel slow and flaky. Keep startup minimal and deterministic.

9) I fixed my prompt, but WSL still takes ~5 seconds to open the first time each day

That’s likely VM bring-up/resume plus systemd (if enabled) plus storage warm-up. Measure with Task 1 and systemd tools. If baseline is slow, look at systemd units, mounts, and host storage/endpoint protection.

10) What’s the safest “escape hatch” when my shell is broken and won’t start?

Start a clean shell: bash --noprofile --norc or zsh -f, then fix or temporarily rename your rc files.

Conclusion: practical next steps

WSL startup speed is not a mystery. It’s a supply chain: VM state, system services, mounts, network, then your shell scripts. The slowest dependency wins. If you want it fast, stop guessing and start isolating.

Do this next, in order:

  1. Measure baseline startup without interactive shell init.
  2. Measure interactive shell startup; if that’s the delta, profile dotfiles and kill prompt/network nonsense.
  3. If baseline is slow, check systemd boot time and slow units; then mounts and DNS.
  4. Move heavy repos to the Linux filesystem and keep /mnt/c for light sharing.
  5. Trim PATH and stop importing the entire Windows ecosystem unless you truly need it.

When you get it right, WSL feels like what it was always supposed to be: a fast, disposable Linux workspace that starts in the time it takes you to think of the next command.

← Previous
WireGuard Roaming: The Fix That Stops Mobile VPN Disconnects
Next →
Passwordless on Windows: Safer, or Just New Problems?

Leave a comment