WSL on Windows Arm: Matching the Host, Kernel, Distro, and Packages
Diagnose WSL architecture on Windows Arm by checking the Windows host, WSL kernel, Linux userspace, and each package's supported architecture.
Windows on Arm and WSL involve several architecture decisions that are easy to collapse into one. The Windows host has an architecture, the WSL 2 kernel is built for a guest architecture, a Linux distribution has an architecture, and each package or binary must support that Linux userspace. Microsoft documents WSL on both x64 and Arm64 CPUs, but that does not make every distribution image, native extension, kernel module, or vendor binary interchangeable.
Start by recording the Windows build and CPU architecture, then inspect WSL and the running Linux guest:
wsl --status
wsl --version
wsl --list --verbose
wsl -d Ubuntu -- uname -m
wsl -d Ubuntu -- sh -lc 'dpkg --print-architecture 2>/dev/null || true'
The distro name in this example is only illustrative. uname -m reports the running kernel’s machine architecture; dpkg --print-architecture reports the package architecture for Debian-derived distributions. These answer different questions. For an executable, inspect the file format as well:
file ./tool
readelf -h ./tool | grep -E 'Class:|Machine:'
The commands are probes, not a guarantee that every utility is preinstalled. Install file and GNU Binutils in the distro if they are absent. The wsl --version option is not available in every older, inbox-serviced WSL installation; if it is rejected, record the Windows build and use the running guest’s uname -r plus the distro’s release and package information instead. Do not infer the Linux guest architecture from the architecture of the Windows terminal process.
Read each architecture report at its own layer
For a useful incident record, distinguish at least four values:
| Layer | Probe | What it tells you |
|---|---|---|
| Windows host | Windows Settings, System, About; wsl --status for WSL status |
The physical/virtual Windows environment and installed WSL servicing state. It does not identify the ELF machine type of a Linux program. |
| Linux kernel | uname -m and uname -r inside the distro |
The machine name and release reported by the running Linux kernel. On Debian Arm64, the machine string is commonly aarch64; that spelling is not the Debian package architecture name. |
| Distribution package database | dpkg --print-architecture and dpkg --print-foreign-architectures |
The native Debian package architecture and any additional architectures configured for package installation. It does not prove that an arbitrary downloaded binary is compatible. |
| Artifact | file, readelf -h, and, for dynamic ELF files, readelf -l |
The file recognizer’s classification, ELF class/machine fields, and the requested program interpreter when present. It does not prove that all runtime libraries or CPU features are available. |
For Debian, the port name is arm64; Debian documentation relates it to 64-bit Arm, while its multiarch name is aarch64-linux-gnu. Similar-looking labels are not interchangeable in every tool: package managers, compiler triples, Linux uname, ELF headers, and release filenames each use their own conventions. Compare values only after identifying which layer emitted them.
wsl --list --verbose is useful for confirming which distro is WSL 1 or WSL 2, but it does not replace the Linux-side checks. WSL 2 runs a Linux kernel in a lightweight managed VM; the distro’s package database and files remain a separate userspace layer. A package upgrade can update user-space libraries without changing the WSL kernel. Conversely, wsl --update services the WSL platform/kernel but does not rebuild a native extension inside every distro.
Verify the executable, not just the machine label
An ELF header can reveal that a file is 64-bit AArch64 or x86-64, but compatibility also depends on the ABI, dynamic loader, shared libraries, and instruction-set assumptions. file is a useful classifier, not a validator. readelf -h shows ELF class and machine; readelf -l can show the PT_INTERP path requested by a dynamically linked program. If that interpreter is absent, Linux can report “No such file or directory” even though the executable path itself exists. A wrong or unrecognized executable format can instead produce an execution-format error. Check the interpreter and runtime dependencies before rebuilding the WSL distro.
For a quick local report, keep the probes together and label their output:
printf 'kernel: '; uname -m
printf 'kernel release: '; uname -r
printf 'Debian package architecture: '
dpkg --print-architecture 2>/dev/null || printf 'not a Debian-derived distro\n'
printf 'compiler target: '
${CC:-cc} -dumpmachine 2>/dev/null || printf 'compiler query unavailable\n'
file ./tool
readelf -h ./tool | grep -E 'Class:|Machine:'
readelf -l ./tool | grep 'Requesting program interpreter' || true
The compiler target query is useful during builds, but it reports how that compiler was configured, not what a pre-existing artifact contains. A compiler wrapper, target flag, linker choice, or downloaded binary can diverge from the default target. Inspect the emitted file and then run it in a disposable test environment matching the intended guest. When packaging a deliverable, record the compiler version, target triple, sysroot, libc baseline, and any CPU-specific flags alongside the artifact checksum.
Build for the guest ABI, not assumptions about Windows
On an Arm64 Windows machine, prefer a distro and package repository that explicitly provide Arm64 builds when the Linux guest is Arm64. Build native extensions inside that distribution or use a cross-toolchain that names the intended target. A Windows x64 application running through Windows compatibility translation is not evidence that an x86-64 ELF program will run inside an Arm64 Linux guest. Windows app emulation and Linux executable support are different execution paths; any Linux user-mode emulator or compatibility mechanism must be separately present and tested in the guest.
This distinction matters for language runtimes as well as standalone programs. A Python wheel containing native code, a Node.js native addon, a Rust or Go executable, and a vendor command-line binary each have a platform contract. A pure-language package may work across CPU architectures while one dependency with a compiled extension does not. Prefer the distro’s package manager or the project’s supported Arm64 build, and inspect the artifact actually installed rather than relying on the package’s display name.
If a program fails with an execution-format error, compare its ELF Machine field with the running guest and check whether a documented emulation path is installed. If the error says a file or interpreter is missing, inspect the exact path, script shebang or ELF interpreter, and filesystem permissions before attributing it to Arm incompatibility. If the program starts but crashes on a particular operation, architecture may already be correct; investigate missing libraries, unsupported CPU instructions, native extensions, or assumptions about device access. Reproduce with a minimal invocation and capture the exact error instead of reinstalling WSL as the first response.
Kernel modules require even closer matching: they must target the WSL kernel’s architecture, configuration, and ABI. External-module builds use kernel build artifacts for the running kernel; the kernel’s Kbuild documentation describes building against /lib/modules/$(uname -r)/build and notes that module-version checks depend on kernel build data. Do not assume a regular distro kernel-header package matches Microsoft’s WSL kernel. First check whether the running WSL kernel exposes the required headers/configuration and supports the module feature. A distro package update does not replace Microsoft’s WSL kernel, and a custom-kernel workflow must preserve a supported rollback path. Check WSL’s own version and kernel-update channel before diagnosing a package failure as a virtualization defect.
This is a boundary, not a recipe to load arbitrary kernel code. A module may compile successfully and still fail to load if the running kernel lacks an expected symbol, configuration option, or compatible version data. Keep the kernel release, module build tree, compiler target, and module load result in the same evidence record. If the workload can be implemented in user space, avoid introducing an out-of-tree module merely to work around a userspace package mismatch.
Preserve reproducibility
For build and CI reports, record the Windows build, WSL version, distro release, uname -m, package architecture, compiler target triple, and relevant kernel version. Test prebuilt artifacts on the exact guest architecture instead of relying on the host label. When distributing multi-architecture Linux packages, publish separate artifacts or a verified manifest and make selection explicit.
A practical acceptance matrix should answer these questions before a toolchain or distro change:
- Does the Windows release support the intended WSL mode on this Arm64 device?
- Is the distro running as WSL 1 or WSL 2, and what does its running kernel report?
- Which package architecture does the distro select, and are foreign architectures configured intentionally?
- What architecture, ELF class, interpreter, and library baseline does the executable require?
- Was the artifact built for the guest ABI, or is an explicit and supported emulation mechanism part of the design?
- If kernel code is involved, are the exact WSL kernel build artifacts and configuration available, and is module loading supported?
Test these questions with a representative binary or native extension, not only a shell command that prints architecture labels. Record success on a clean distro or CI image as well as on a developer workstation. If the same repository must support both x64 and Arm64 WSL guests, run separate jobs or matrix entries and publish artifacts with unambiguous architecture names. A single successful build on Windows Arm proves only the path exercised by that build.
The safe rule is simple: validate every layer independently. WSL supports multiple host architectures; the chosen Linux environment and its dependencies still have their own binary compatibility contracts.
Related:
- Running a Custom WSL 2 Kernel Without Losing the Supported Rollback Path
- How to Install and Manage Multiple Linux Distros in WSL
Sources:
- Microsoft Learn: WSL FAQ and supported CPU architectures
- Microsoft Learn: Manual WSL installation and architecture-specific kernel packages
- Microsoft Learn: What is Windows Subsystem for Linux?
- Microsoft Learn: How emulation works on Windows on Arm
- Microsoft: WSL2 Linux kernel source and build configuration
- Debian Installation Guide: Supported 64-bit Arm architecture
- Debian dpkg manual: package architecture reporting
- Linux man-pages: uname(1)
- GNU Binutils: readelf
- Linux Kernel documentation: Building external modules
- Linux man-pages: execve(2)
- GCC documentation: Developer options and -dumpmachine