Configuration reference
What each setting does, its default, and when to change it. Recipes link here; you rarely need to read it top to bottom.
Config files
.wslconfig settings#
Every setting in %USERPROFILE%\.wslconfig, the file that sizes and networks the whole WSL2 virtual machine, with defaults and when to change each one.
.wslconfig is one file on the Windows side that configures the single virtual machine all your distros share. Per-distro settings (systemd, default user, PATH) live in /etc/wsl.conf instead.
Changes apply only after a full restart of the VM: run wsl --shutdown in PowerShell, wait about 8 seconds, then open a terminal again. Closing terminal tabs is not enough. If nothing changes, see .wslconfig changes do nothing.
Example file#
A sensible starting point for a 32 GB development machine. Every line is optional; leave out what you don’t need.
# Applies after: wsl --shutdown (wait ~8 s, then reopen a terminal)
[wsl2]
memory=16GB # ~50–75% of your RAM
processors=8 # all threads, or all minus 2
swap=8GB # ~25–50% of memory
networkingMode=mirrored # WSL shares Windows' IPs; localhost works both ways
dnsTunneling=true # fixes DNS on VPNs and odd networks
firewall=true # Windows firewall rules apply to WSL
autoProxy=true # use the Windows proxy settings
guiApplications=true # WSLg: run Linux GUI apps
nestedVirtualization=true # KVM / Android emulators inside WSL
[experimental]
autoMemoryReclaim=dropcache # give idle RAM back to Windows
sparseVhd=true # virtual disk shrinks when you delete files
hostAddressLoopback=true # Windows can reach services bound to WSL's LAN IPSizing memory and swap#
| Your RAM | memory | swap |
|---|---|---|
| 16 GB | 8–10GB | 4GB |
| 32 GB | 16–24GB | 8GB |
| 64 GB | 32–48GB | 8–16GB |
Leave Windows enough for the browser, editor UI and any Android emulator, which all run on the Windows side. How to apply and check a cap: Limit WSL memory and CPU.
[wsl2] section#
memory#
Default: 50% of Windows RAM. The most memory the VM may use. Linux fills spare RAM with file cache, so without a cap vmmemWSL can grow until Windows feels slow. Lower it if Windows apps starve; raise it for large builds, many containers or big test suites. Recipe: Limit WSL memory and CPU. Symptom: WSL uses too much memory.
processors#
Default: all logical processors. The number of CPU threads Linux sees (nproc). Lower it only if Windows needs guaranteed headroom while builds run, for example during calls or screen recording.
swap#
Default: 25% of memory, rounded up to a whole GB. Swap space on a separate virtual disk. Set it to 25–50% of memory. swap=0 disables it, which turns memory pressure into processes being killed instead of slowing down.
networkingMode#
Default: nat. mirrored makes WSL share Windows’ network interfaces and IP addresses: localhost works in both directions and devices on your LAN can reach WSL ports. Needs Windows 11 22H2 or later. Comparison and which mode suits which connection: Networking modes. Recipe: Turn on mirrored networking.
dnsTunneling#
Default: on in current WSL releases (older releases: off). DNS requests from Linux are answered by Windows instead of over the virtual network, so DNS keeps working with VPNs and unusual network setups. Leave it on unless you have a specific reason. Symptom: DNS fails after connecting to a VPN.
firewall#
Default: true on Windows 11 22H2+. Applies Windows Firewall and Hyper-V firewall rules to WSL traffic. In mirrored mode this is what blocks a phone from reaching your dev server until you open the port: Open a WSL port to your local network.
autoProxy#
Default: true. Passes the Windows HTTP proxy settings into Linux as environment variables. Useful on corporate networks; harmless elsewhere.
guiApplications#
Default: true. WSLg: Linux GUI apps such as gitk or meld open as normal Windows windows. Set false to save a little memory if you never use them.
nestedVirtualization#
Default: true on Windows 11. Lets Linux use KVM, for example to run an Android emulator or a VM inside WSL. Most setups run the Android emulator on Windows instead: Run React Native with the Android emulator.
localhostForwarding#
Default: true. In NAT mode, a server listening in WSL is reachable from Windows at localhost. Ignored in mirrored mode, where localhost is shared anyway.
[experimental] section#
Experimental keys can change or graduate between WSL releases. Check them against the official WSL config docs or the WSL Settings app.
autoMemoryReclaim#
Values: disabled, gradual, dropcache. The default differs between WSL versions. Gives memory Linux is only using as cache back to Windows. gradual releases it slowly; dropcache drops the cache once WSL has been idle. Either keeps vmmemWSL from holding RAM it no longer needs.
sparseVhd#
Default: false. New distros get a sparse virtual disk that shrinks when you delete files, instead of only ever growing. Recipe: Shrink the WSL virtual disk.
hostAddressLoopback#
Default: false. Mirrored mode only. Lets Windows (and containers) reach a service in WSL through the machine’s LAN IP, not just through localhost. Turn it on if a tool insists on connecting to your LAN address.
Config files
/etc/wsl.conf settings#
Per-distro settings in /etc/wsl.conf (systemd, default user, Windows PATH, drive mounts, DNS file) with defaults and the consequence of changing each.
/etc/wsl.conf lives inside each distro and only affects that distro. Settings for the whole VM (memory, networking) are in .wslconfig.
Edit it with sudo (for example sudo nano /etc/wsl.conf). Changes apply after wsl --shutdown in PowerShell. Make a copy before editing, so a typo is easy to undo:
sudo cp /etc/wsl.conf /etc/wsl.conf.bakExample file#
[boot]
systemd=true
[user]
default=yourname # also used when you restore a backup
[interop]
enabled=true
appendWindowsPath=true # false = faster shell, but code/explorer.exe disappear
[automount]
enabled=true
options="metadata,umask=22,fmask=11" # real Linux permissions on /mnt/c
[network]
generateResolvConf=true # let WSL manage /etc/resolv.confSettings#
boot systemd#
[boot] systemd=true. Default: on for Ubuntu installed with wsl --install. Runs systemd as PID 1, which Docker Engine, timers (fstrim.timer) and systemctl need. Check with ps -p 1 -o comm=: it should print systemd. If systemctl is-system-running says degraded, see systemd reports degraded.
user default#
[user] default=yourname. Default: the user you created on first launch, stored by Windows rather than in this file. Write it here anyway: a distro restored with wsl --import reads it from this file, and otherwise logs you in as root. See Back up and restore your WSL distro.
interop enabled#
[interop] enabled=true. Default: true. Lets Linux start Windows programs (explorer.exe ., clip.exe, code). Turning it off breaks the VS Code code command and every Windows tool call.
interop appendWindowsPath#
[interop] appendWindowsPath=true. Default: true. Adds the whole Windows PATH to Linux. Convenient, because code and explorer.exe just work, but it slows command lookup and can let the Windows node or npm shadow the Linux ones (Node or npm is the Windows one).
Setting it to false is optional tuning with a consequence: code, explorer.exe and powershell.exe stop being found until you add them back by hand. How to do that properly: Call Windows apps without the Windows PATH.
automount options#
[automount] options="metadata,umask=22,fmask=11". Default: no metadata. Without metadata, files under /mnt/c all look executable and permission changes don’t stick. With it, chmod works there and Git stops reporting mode changes. Code should still live on the Linux filesystem: Where files live.
network generateResolvConf#
[network] generateResolvConf=true. Default: true. WSL rewrites /etc/resolv.conf on every start, which is why hand edits to that file disappear. Only set false if you deliberately manage DNS yourself, as a last resort described in DNS fails after connecting to a VPN. Prefer dnsTunneling in .wslconfig.
network hostname#
[network] hostname=devbox. Default: the Windows computer name. Changes the Linux hostname, which shows up in prompts and in key titles like $(hostname) WSL.
Concepts
Where files live#
Why code belongs on the Linux filesystem (~/dev) and not on /mnt/c, with a detailed comparison, a layout that scales, and how to reach the files from Windows.
Linux files sit on an ext4 virtual disk (ext4.vhdx). /mnt/c is the Windows drive, reached from Linux over a network-style file-sharing bridge. Every file operation that crosses that bridge is slow, and file-change events often don’t cross it at all.
The rule: keep code in your Linux home (~/dev), and run tools from Linux on it. The baseline setup creates that folder.
Comparison#
~/dev (Linux disk) | /mnt/c (Windows disk) | |
|---|---|---|
pnpm install, builds, git status | Native speed | 5–20× slower |
| File watchers (Vite, Next, Metro) | Instant reload | Often miss changes |
| Permissions, symlinks, exec bits | Correct | Emulated, Git noise |
| Docker bind mounts | Fast | Slow |
| Line endings | LF | CRLF slips in |
| Defender scanning | Not scanned per file | Every file scanned |
The reverse also holds: Windows tools working on Linux files through \\wsl.localhost are slow. That’s why the editor runs its language servers inside Linux (Open a WSL project in your editor).
A layout that scales#
~/dev/
├── projects/ # repos, optionally projects/<org>/<repo>
├── infra/ # shared docker-compose (Postgres, Redis…)
└── sandbox/ # throwaway experimentsinfra/ is where shared Postgres and Redis go.
Reaching the files from Windows#
- In Explorer:
\\wsl.localhost\Ubuntu\home\<you>\dev. Pin it to Quick Access. - From the Linux shell:
explorer.exe .opens the current folder. - Convert paths:
wslpath -w .gives the Windows path,wslpath 'C:\Users'the Linux one.
Don’t mix tools across the boundary#
Use Windows Git and Node on Windows files, and WSL Git and Node on WSL files. Running Windows Git on a Linux repo (or the reverse) rewrites line endings and file modes and confuses lockfiles. If it already happened: Every file shows as changed.
/mnt/c is fine for downloads, design assets and one-off copies:
cp /mnt/c/Users/<you>/Downloads/x.zip ~/dev/sandbox/Why /mnt/c is slowWhy
WSL2 is a real Linux kernel in a lightweight VM. Its own disk is a block device it controls directly. Windows drives are shared into the VM over the 9P protocol: every stat, open and read becomes a request to a server on the Windows side. Package managers and Git perform tens of thousands of those per command. Windows also doesn’t deliver its file-change notifications as Linux inotify events, so watchers on /mnt/c fall back to polling or miss changes.
Concepts
Networking modes#
How WSL2 networking works in NAT and mirrored mode, what each can reach, and which recipe to use for each connection scenario.
WSL2 runs in a lightweight VM, so it has its own network stack. networkingMode in .wslconfig decides how that stack is joined to Windows.
NAT and mirrored compared#
| NAT (default) | Mirrored | |
|---|---|---|
| WSL’s IP address | Private 172.x address, changes on restart | Same IPs as Windows |
Windows → WSL on localhost | Yes (localhost forwarding) | Yes |
WSL → Windows on localhost | No; use the Windows host IP | Yes |
| Phone / LAN → WSL | Only via netsh portproxy rules | Yes, after a firewall rule |
| VPN compatibility | Often breaks DNS or routing | Better, with dnsTunneling |
| Requires | Any WSL2 | Windows 11 22H2+ |
Mirrored is the better default for development on Windows 11. Recipe: Turn on mirrored networking.
Which recipe for which connection#
| You want to reach… | From… | Recipe |
|---|---|---|
| A dev server in WSL | A Windows browser | Works in both modes. Not working? A port is already in use |
| A service on Windows | WSL | Turn on mirrored networking |
| A dev server in WSL | A phone or another computer | Open a WSL port to your local network, then Open your Expo app on a phone or Test a local HTTPS site on your phone |
| A service in WSL | A Docker container | Connect a container to a service in WSL |
| The internet | WSL, while on a VPN | DNS fails after connecting to a VPN |
The Windows adb server | WSL | Run React Native with the Android emulator |
Listening addresses#
Whether something is reachable also depends on the address the server listens on. Check with:
ss -tlnp # 0.0.0.0 / [::] = reachable from LAN; 127.0.0.1 = local only| Listens on | Reachable from |
|---|---|
127.0.0.1 / ::1 | The same machine (Windows and WSL share it in mirrored mode) |
0.0.0.0 / [::] | Every interface: containers, LAN, phones (subject to firewall) |
Most dev servers listen on localhost by default and need a flag such as --host to accept outside connections.
How NAT mode forwards localhostWhy
In NAT mode, WSL gets an address on a private virtual switch. A helper (wslrelay) watches for ports opening in Linux and listens on the same port on Windows’ localhost, forwarding traffic in. That’s why localhost:3000 works from a Windows browser, and why Get-NetTCPConnection on Windows shows wslrelay owning ports your Linux server opened. Nothing forwards in the other direction or from the LAN.
Concepts
Kernel and resource limits#
The Linux kernel, file-handle and Docker limits that matter for large repositories, what they default to, and which recipe changes each.
Ubuntu’s defaults suit small servers, not monorepos with 200,000 files and several watchers. You only need to change these when you hit the matching symptom; none are part of the baseline setup.
Kernel settings (sysctl)#
Persisted in a file under /etc/sysctl.d/, applied with sudo sysctl --system. Check a current value with sysctl <name>.
| Setting | Typical default | Suggested | What it limits |
|---|---|---|---|
fs.inotify.max_user_watches | 8192–65536 | 524288 | Files all watchers together can watch |
fs.inotify.max_user_instances | 128 | 1024 | Separate watcher processes |
fs.file-max | Large on modern kernels | 2097152 | Open files system-wide |
vm.max_map_count | 65530 | 262144 | Memory maps per process (Elasticsearch, some JVM tools) |
vm.swappiness | 60 | 10 | How eagerly Linux swaps instead of dropping cache |
Watchers#
ENOSPC: System limit for number of file watchers reached, or hot reload stops on a large repo. Recipe: Raise file-watcher limits. Symptom: Changes do not trigger hot reload.
vm.max_map_count#
Only needed for Elasticsearch/OpenSearch (which refuse to start below 262144) and some JVM tools. Set it in the same sysctl file as the watcher limits.
vm.swappiness#
Lower values keep build processes in RAM rather than swap. Only worth changing if you see heavy swap use with a memory cap in .wslconfig.
Open-file limits (nofile)#
The per-process limit on open files, checked with ulimit -n. Ubuntu’s soft default is 1024. Jest, Metro and bundlers on large repos can exceed it and fail with EMFILE: too many open files. It has two places: PAM limits for login shells and DefaultLimitNOFILE for systemd services. Recipe: Raise open-file limits. Symptom: Too many open files.
Docker limits#
Set in /etc/docker/daemon.json (Docker Engine in WSL).
| Setting | Default | Suggested | Why |
|---|---|---|---|
log-driver / log-opts | json-file, unlimited | local, max-size: 20m, max-file: 3 | Container logs otherwise grow without bound |
builder.gc.defaultKeepStorage | Unlimited | 30GB | Caps the BuildKit build cache |
Recipe: Install Docker in WSL. Disk symptom: Windows disk space is running out.
Disk TRIM#
fstrim.timer (systemd) tells the virtual disk which blocks are free, so a sparse VHD can actually shrink. Recipe: Shrink the WSL virtual disk.
VM-wide limits#
Memory, CPU and swap for the whole VM are set in Windows, not in Linux: .wslconfig and Limit WSL memory and CPU.