Troubleshooting WSL Ubuntu Installation Timeouts and Node.js Dependency Stalls

Setting up a development environment on Windows 11 using the Windows Subsystem for Linux (WSL) is a standard practice. However, the process can easily be derailed by “operation timed out” errors during Ubuntu installation or “downloading stuck” issues when configuring Node.js dependencies inside the Linux subsystem.

These interruptions are rarely due to user error. Instead, they stem from a complex intersection of network restrictions, WSL’s unique network architecture, and the strict file permission requirements of different tools. This guide walks through the entire process—from installing WSL and extracting ISO images to resolving Node.js dependency stalls and configuring proxy interoperability—helping you quickly identify and resolve these bottlenecks.

1. Diagnosing WSL Ubuntu Installation Timeouts

When you run wsl --install -d Ubuntu --web-download in PowerShell and encounter a timeout, the first step is to understand where the failure occurs. Usually, the installer attempts to download WSL core components or the Ubuntu root filesystem from external servers, which your current network environment cannot stably access.

Before attempting to download anything, you must verify the state of your current WSL installation. Try running wsl --version or wsl --set-default-version 2. If the system returns a block of basic help text instead of a version number, you are using the legacy “inbox version” bundled with Windows. This older version does not support modern WSL commands and cannot be upgraded directly via the command line.

First, confirm your Windows version meets the requirements by running winver. A build like Windows 11 23H2 (Build 22631) fully supports WSL2. If the version is correct, you need to manually enable the underlying Windows features.

Enabling Required Windows Features

Open PowerShell as an administrator and run the following commands to enable the subsystem and virtual machine platform:

dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

After execution, you must restart your computer for these底层 changes to take effect.

2. Bypassing Network Restrictions: Acquiring Ubuntu Packages

After restarting, standard commands like wsl --install or downloading the latest WSL package from GitHub will likely still time out due to persistent network restrictions. In this scenario, an offline or semi-offline strategy is required.

There are several ways to obtain the Ubuntu files. You can choose based on your actual network connectivity:

Download Source File Format Estimated Size Characteristics
Official Microsoft short link .appx Varies May involve multiple redirects, prone to timeouts
Official Ubuntu WSL rootfs .tar.gz ~180MB Ideal for WSL import, but often blocked externally
Domestic mirror sites (e.g., Tsinghua) .iso ~2.1GB Stable download, but requires additional extraction steps

You can use a simple PowerShell script to quickly test which addresses are accessible:

$urls = @(
    "https://cloud-images.ubuntu.com/wsl/jammy/current/ubuntu-jammy-wsl-amd64-wsl.rootfs.tar.gz",
    "https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/22.04/ubuntu-22.04.5-live-server-amd64.iso",
    "https://mirrors.ustc.edu.cn/ubuntu-releases/22.04/ubuntu-22.04.5-live-server-amd64.iso"
)
foreach ($url in $urls) {
    try {
        $r = Invoke-WebRequest -Uri $url -Method Head -UseBasicParsing -TimeoutSec 10
        Write-Host "Accessible: $url"
    } catch {
        Write-Host "Inaccessible: $url"
    }
}

If external rootfs mirrors all time out, your most reliable fallback is downloading the full ISO image (for example, downloading to C:\WSL\ubuntu.iso via the Tsinghua mirror). While 2.1GB might seem like overkill for a WSL installation, it is the most stable method to bypass network blocks.

3. Extracting rootfs from ISO and the Windows Permission Trap

WSL’s --import command only accepts tar-formatted compressed archives, not ISOs. Therefore, you must extract a core filesystem file named ubuntu-server-minimal.squashfs from the ISO and convert it to tar.gz.

The Permission Trap in Windows Extraction

Many users instinctively use tools like 7-Zip in Windows to extract the squashfs file. However, this triggers a fatal issue: Linux file permissions are lost.

When you extract and repackage the files in Windows and import them into WSL, you can enter the system, but executing any command (including /bin/sh) will result in a Permission denied error. This happens because Windows file systems (NTFS/ReFS) do not support Linux file execution permission markers. The execution bits are stripped during the extraction process.

Preserving Permissions Using docker-desktop

If you have Docker Desktop installed, it comes with a minimal WSL distribution named docker-desktop. You can borrow this native Linux environment to properly mount and package the files.

Step 1: Copy the squashfs file in PowerShell

Extract the target file from the mounted ISO (assuming it is mounted on the F: drive) to your C: drive:

Copy-Item "F:\casper\ubuntu-server-minimal.squashfs" "C:\WSL\ubuntu.squashfs"

Step 2: Mount and package inside docker-desktop

Enter the docker-desktop environment:

wsl -d docker-desktop

Inside docker-desktop, create a mount point and mount the file. Note that you cannot mount files directly on Windows drive paths; you must operate within the C: drive mapped path (since docker-desktop’s tmp space is limited):

mkdir -p /mnt/host/c/WSL/rootfs_mount
mount -t squashfs /mnt/host/c/WSL/ubuntu.squashfs /mnt/host/c/WSL/rootfs_mount

Once mounted successfully, package it into a format WSL recognizes:

tar -czf /mnt/host/c/WSL/ubuntu2204.tar.gz -C /mnt/host/c/WSL/rootfs_mount .

Step 3: Exit and import into WSL

exit
wsl --unregister Ubuntu-22.04
wsl --import Ubuntu-22.04 C:\WSL\Ubuntu22 C:\WSL\ubuntu2204.tar.gz
wsl -d Ubuntu-22.04

Ubuntu imported this way retains complete file permissions and can execute system commands normally.

4. Identifying the Playwright Download Bottleneck

After successfully entering WSL and running installation scripts for AI agent tools (like Hermes Agent), progress frequently freezes entirely at the “Installing Node.js dependencies (browser tools)” step.

Looking closely at the terminal output, you will notice the line above the freeze is usually:
Downloading Chrome for Testing 147.0.7727.15 (playwright chromium v1217) from https://cdn.playwright.dev/...

This happens because, during Node.js dependency installation, Playwright—an automation testing library—attempts to download a complete Chromium browser from cdn.playwright.dev or its redirected storage.googleapis.com. In restricted network environments, these domains are inaccessible, causing the process to hang indefinitely.

5. Workarounds: Using Mirrors and Environment Variables

For Playwright download stalls, there are several conventional workarounds you can try in order:

1. Configure a Domestic npm Mirror

If the npm packages themselves are downloading slowly, switch the source:

npm config set registry https://registry.npmmirror.com

2. Set a Playwright Download Mirror

Playwright supports specifying a download host via environment variables:

PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/ npx playwright install chromium

Note, however, that specific versions of the browser files might not be synced to domestic mirrors, resulting in “file not found” errors.

3. Skip the Browser Download

If the core functionality of the tool you are using does not depend on a browser (e.g., no web scraping or screenshots required), the easiest method is to skip it entirely:

export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1

If you are running an installation script, the script might spawn new child processes. This means environment variables exported manually in your terminal might not be inherited by those child processes, causing the download to stall anyway.

6. Navigating WSL and Windows Proxy Complexities

When mirrors fail to meet your needs and you must route WSL traffic through a proxy to access external networks, WSL’s network architecture introduces several complications.

Host IP Extraction and Port Conflicts

In the default NAT network mode, WSL cannot use 127.0.0.1 to access a proxy running on Windows. You need to obtain the Windows host’s IP within the virtual network:

HOST_IP=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}')
export https_proxy=http://${HOST_IP}:7897
export http_proxy=http://${HOST_IP}:7897

Even after setting the correct IP and proxy port, if you still encounter ECONNREFUSED errors, there are usually two reasons:

  1. Windows Firewall Interception: You need to add an inbound rule for the proxy port in an Administrator PowerShell:
New-NetFirewallRule -DisplayName "Allow WSL Proxy" -Direction Inbound -LocalPort 7897 -Protocol TCP -Action Allow
  1. Hard Limitations of NAT Mode: Even with the firewall bypassed, the system might explicitly state that “localhost proxy is not supported in NAT mode WSL.” In this case, you must enable mirrored networking mode for WSL.

Enabling Mirrored Networking Mode

Create or edit the .wslconfig file in your Windows user directory (e.g., C:\Users\<YourUsername>\):

[wsl2]
networkingMode=mirrored

Then, completely restart WSL in PowerShell:

wsl --shutdown
wsl

After restarting, WSL’s network is fully integrated with the Windows host. You can now use 127.0.0.1 directly as the proxy address, eliminating the need to look up the host IP:

export https_proxy=http://127.0.0.1:7897
export http_proxy=http://127.0.0.1:7897
npx playwright install chromium

7. The Fallback Strategy: Spoofing the Browser Check

If proxy configurations still fail to connect due to various underlying issues, and you are certain you do not need browser automation features, you can use a technical workaround to trick the installation program’s validation mechanism.

Playwright checks a specific cache directory for the existence of an executable file when running. You can manually create an empty script file to act as this executable:

mkdir -p ~/.cache/ms-playwright/chromium-1217/chrome-linux
echo '#!/bin/bash' > ~/.cache/ms-playwright/chromium-1217/chrome-linux/chrome
chmod +x ~/.cache/ms-playwright/chromium-1217/chrome-linux/chrome

The principle here is to bypass the environment validation logic. When the tool starts and performs a dependency integrity check, it assumes the browser is already present. It will only throw an error when a web operation is actually triggered. For non-browser scenarios like pure text processing or code generation, this perfectly circumvents the download hang.

8. Configuring Base Environments and Accelerating Package Managers

In a minimal Ubuntu environment imported through the extreme methods described above, you must resolve the software source issue before installing any software. The minimal version does not have apt sources configured by default, so running apt-get install will result in a “package has no installation candidate” error.

Configuring apt Sources

Replace /etc/apt/sources.list with a stable domestic mirror (like Aliyun):

cat > /etc/apt/sources.list << 'EOF'
deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
EOF
apt-get update

Accelerating Node.js and Python Package Managers

When running installation scripts, if you notice the download of uv (a Python package manager) or Node.js is extremely slow, you can manually install them via domestic mirrors beforehand. The script will detect their existence and skip the download.

For example, manually downloading and extracting Node.js to the system directory:

curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/nodejs-release/v20.19.2/node-v20.19.2-linux-x64.tar.xz -o /tmp/node.tar.xz
tar -xf /tmp/node.tar.xz -C /usr/local --strip-components=1

When installing Python dependencies, specify the Tsinghua source:

pip3 install uv -i https://pypi.tuna.tsinghua.edu.cn/simple

9. Elegantly Binding Virtual Environments to Global Commands

Tools like Hermes Agent are typically installed within a Python virtual environment (venv). This creates a dilemma: the hermes command only works when you have executed source venv/bin/activate in the project directory. If you switch to another directory, the system prompts that the command is not found.

If you want to invoke hermes directly from any path like a system command, without polluting the global configuration with a virtual environment, you can create a wrapper script:

sudo tee /usr/local/bin/hermes << 'EOF'
#!/bin/bash
source ~/.hermes/hermes-agent/venv/bin/activate
python ~/.hermes/hermes-agent/run_agent.py "$@"
EOF
sudo chmod +x /usr/local/bin/hermes

The essence of this script is registering an entry point at the system level in /usr/local/bin. Every time you execute hermes, it automatically activates the corresponding virtual environment in the background and passes your input arguments ($@) verbatim to the actual Python execution script. This perfectly balances environmental isolation with ease of use.


Frequently Asked Questions

What should I do when WSL Ubuntu installation times out?
This usually occurs because your network cannot connect to Microsoft’s or GitHub’s download servers. Avoid repeatedly attempting online installations. The most effective method is to manually enable WSL features, restart, and then perform an offline import using an Ubuntu ISO or rootfs file downloaded from a domestic open-source mirror.

How do I fix Playwright getting stuck when downloading Chrome?
The installation script is trying to download browser binaries from a restricted external CDN. You can try setting the PLAYWRIGHT_DOWNLOAD_HOST environment variable to point to a domestic mirror. If you do not need web automation features, setting PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 is the fastest workaround. Alternatively, you can create a dummy file with the same name in the cache directory to pass the environment check.

How can I use my Windows proxy in WSL?
If WSL is using the default NAT network mode, you need to read /etc/resolv.conf to get the Windows host IP and set your proxy to http://Host-IP:Port. You must also ensure the Windows Defender Firewall allows inbound traffic on that port. A more modern approach is to set networkingMode=mirrored in the Windows .wslconfig file. Once enabled, WSL can use 127.0.0.1 directly as the proxy address.

Why does my imported Ubuntu show “Permission denied” errors?
If you used 7-Zip or similar Windows tools to extract the squashfs file from the ISO before importing, the Linux executable file permissions were stripped. You must use a proper Linux environment (like the docker-desktop WSL distribution included with Docker Desktop) to mount the original file using mount -t squashfs, package it with tar, and then import it. This preserves the permission attributes.

How can I make the hermes command available in any directory?
Do not add the virtual environment’s bin directory to your global PATH. Instead, use sudo tee to write a Bash script to /usr/local/bin/hermes. The script should first source the corresponding virtual environment, then execute the main program, and finally grant the script executable permissions.