I have the source code of a hello world kernel module that works in Ubuntu 20 in a laptop. Now I am trying to compile the same code in Ubuntu 20 but inside WSL2. For that I am using this:

make -C /sys/modules/$(shell uname -r)/build M=$(PWD) modules

The problem is that /lib/modules is empty. It seems that WSL2 does not bring anything in /lib/modules/4.19.104-microsoft-standard/build

I tried getting the headers using:

sudo apt search linux-headers-`uname -r`

Sorting... Done
Full Text Search... Done

But nothing get's populated in the modules folder

Is there anything I need to do in order that folder contains all required modules?


Getting closer thanks to @HannahJ.

I am doing:

> sudo make -C /home/<user>/WSL2-Linux-Kernel M=$(pwd) modules

SL2-Linux-Kernel M=$(pwd) modules
make: Entering directory '/home/<user>/WSL2-Linux-Kernel'
  CC [M]  /home/<user>/containers-assembly-permissionsdemo/demo-2/lkm_example.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/<user>/containers-assembly-permissionsdemo/demo-2/lkm_example.mod.o
  LD [M]  /home/<user>/containers-assembly-permissionsdemo/demo-2/lkm_example.ko
make: Leaving directory '/home/<user>/WSL2-Linux-Kernel'

At the end, I get the lkm_example.ko file created.

After that:

> sudo insmod lkm_example.ko
insmod: ERROR: could not insert module lkm_example.ko: Invalid module format

> dmesg
[200617.480635] lkm_example: no symbol version for module_layout
[200617.480656] lkm_example: loading out-of-tree module taints kernel.
[200617.481542] module: x86/modules: Skipping invalid relocation target, existing value is nonzero for type 1, loc 0000000074f1d70f, val ffffffffc0000158

> sudo modinfo lkm_example.ko
filename:       /home/<user>/containers-assembly-permissionsdemo/demo-2/lkm_example.ko
version:        0.01
description:    A simple example Linux module.
author:         Carlos Garcia
license:        GPL
srcversion:     F8B272146BAA2381B6332DE
retpoline:      Y
name:           lkm_example
vermagic:       4.19.84-microsoft-standard+ SMP mod_unload modversions

This is my Makefile

obj-m += lkm_example.o
    make -C /home/<usr>/WSL2-Linux-Kernel M=$(PWD) modules
    make -C /home/<usr>/WSL2-Linux-Kernel M=$(PWD) clean
    # We put a — in front of the rmmod command to tell make to ignore
    # an error in case the module isn’t loaded.
    -sudo rmmod lkm_example
    # Clear the kernel log without echo
    sudo dmesg -C
    # Insert the module
    sudo insmod lkm_example.ko
    # Display the kernel log
    sudo rm /dev/lkm_example
    sudo rmmod lkm_example

[Edit2] This is my kernel module:

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <asm/uaccess.h>
    #include <linux/init_task.h>
    MODULE_AUTHOR("Carlos Garcia");
    MODULE_DESCRIPTION("A simple example Linux module.");
    /* Prototypes for device functions */
    static int device_open(struct inode *, struct file *);
    static int device_release(struct inode *, struct file *);
    static ssize_t device_read(struct file *, char *, size_t, loff_t *);
    static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
    static int major_num;
    static int device_open_count = 0;
    static char msg_buffer[MSG_BUFFER_LEN];
    static char *msg_ptr;
    /* This structure points to all of the device functions */
    static struct file_operations file_ops = {
        .read = device_read,
        .write = device_write,
        .open = device_open,
        .release = device_release
    /* When a process reads from our device, this gets called. */
    static ssize_t device_read(struct file *flip, char *buffer, size_t len, loff_t *offset)
    /* Called when a process tries to write to our device */
    static ssize_t device_write(struct file *flip, const char *buffer, size_t len, loff_t *offset)
    /* Called when a process opens our device */
    static int device_open(struct inode *inode, struct file *file)
    /* Called when a process closes our device */
    static int device_release(struct inode *inode, struct file *file)
    static int __init lkm_example_init(void)
        major_num = register_chrdev(0, "lkm_example", &file_ops);
        if (major_num < 0)
            printk(KERN_ALERT "Could not register device: % d\n", major_num);
            return major_num;
            printk(KERN_INFO "lkm_example module loaded with device major number % d\n", major_num);
            return 0;
    static void __exit lkm_example_exit(void)
        /* Remember — we have to clean up after ourselves. Unregister the character device. */
        unregister_chrdev(major_num, DEVICE_NAME);
        printk(KERN_INFO "Goodbye, World !\n");
    /* Register module functions */

6 Answers 6


I had to do this for an assignment, so I figure I'll share my solution here.

The base WSL2 kernel does not allow modules to be loaded. You have to compile and use your own kernel build.

How to compile and use a kernel in WSL2

  1. In Ubuntu/WSL:

    sudo apt install build-essential flex bison libssl-dev libelf-dev git dwarves
    git clone https://github.com/microsoft/WSL2-Linux-Kernel.git
    cd WSL2-Linux-Kernel
    cp Microsoft/config-wsl .config
    make -j $(expr $(nproc) - 1)
  2. From Windows, copy \\wsl$\<DISTRO>\home\<USER>\WSL2-Linux-Kernel\arch\x86\boot\bzimage to your Windows profile (%userprofile%, like C:\Users\<Windows_user>)

  3. Create the file %userprofile%\.wslconfig that contains:


    Note: The double backslashes (\\) are required. Also, to avoid a potential old bug, make sure not to leave any trailing whitespace on either line.

  4. In PowerShell, run wsl --shutdown

  5. Reopen your flavor of WSL2

How to compile the module

Note: You'll want to do these from /home/$USER/ or adjust the Makefile to match your location.

  1. Create a Makefile that contains:

        make -C $(shell pwd)/WSL2-Linux-Kernel M=$(shell pwd) modules
        make -C $(shell pwd)/WSL2-Linux-Kernel M=$(shell pwd) clean
  2. Run make

Source for the .wslconfig file steps here.


The Windows Subsystem for Linux version 2 uses a custom Linux kernel from Microsoft that contains all of its drivers compiled in. While it has support for modules, it doesn't contain any, as you can see from the configuration file. Therefore, there's no reason to ship a /lib/modules directory.

In addition, most of the Linux distros available for WSL don't ship their WSL versions with a kernel at all. Since Microsoft ships their own, there's no reason to. Those Linux distros don't ship build packages for Microsoft's kernel because they aren't responsible for it; Microsoft is, and you'd need to talk to them about the packages.

It may be the case that you can load the modules into the kernel if you use the standard tools, but you'll likely need to build against an appropriate source tree. You can either try to find the appropriate version in the GitHub repository I linked to above, or you may need to contact Microsoft and ask for the source under the GPLv2, which they are required to provide you upon request.

I will note that WSL is not designed to allow loading custom kernel modules; it's not designed to be a full Linux environment, but rather to allow folks to develop and run standard Linux applications on Windows. If you want to do Linux kernel development, you'll probably need a full Linux installation.


For those that need to load modules on WSL2:

  1. sudo -e /etc/modules-load.d/modules.conf (or your preferred editor as sudo)
  2. Add the names of the kernel modules you would like to load, one line per module.
  3. Exit WSL.
  4. From PowerShell or CMD, run wsl --shutdown.
  5. Restart WSL. Your module should be loaded.

Tested on WSL2 Ubuntu Focal.


If you just want to get vcan running on wsl2:

$ git clone https://github.com/microsoft/WSL2-Linux-Kernel
$ cd WSL2-Linux-Kernel
$ git checkout `uname -r`
$ cat /proc/config.gz | gunzip > .config
$ make prepare modules_prepare
$ make menuconfig  # select vcan and can here (SEE BELOW)
$ make modules
$ make modules_install
$ modprobe vcan
$ ip link add dev vcan0 type vcan
$ ip link set up vcan0
$ apt install can-utils

In the menuconfig (this is where you look for the thing you want to run):

For VCAN on wsl Linux Kernel Configuration >Networking support >CAN bus subsystem support >CAN device drivers >Platform CAN drivers with Netlink support >CAN bit-timing calculation >TI High End CAN controller (HECC)

  • 3
    Apart from dealing with Linux kernel modules on WSL2, this seems unrelated to the specific issue in the question. The question is unrelated to "vcan".
    – Kusalananda
    Commented May 12, 2022 at 18:25
  • I actually posted this to wrong post and now can't find the correct one, but this page was linked to a bunch of other pages for people using wsl with socketCan since you have to make a new kernel or what have you
    – XdoesTech
    Commented May 12, 2022 at 19:53
  • If using Debian on WSL2, you'll want to do sudo apt-get install build-essential flex bison libssl-dev bc libelf-dev libncurses-dev Commented Jul 30, 2022 at 19:56

I was trying to do a Yocto build that worked fine on Ubuntu 22.04 native but failed on WSL2 because it couldn't find /lib/modules/ murata-wireless/cyw-fmac - Build fails on WSL2

I eventually found this workaround:

sudo apt-get install -y linux-headers-generic
ll /lib/modules
# Note the directory that has been installed here, e.g. `5.15.0-67-generic/
# Use "uname -r" or note the directory the build above failed to find, e.g. `/lib/modules/`
sudo ln -s /lib/modules/5.15.0-67-generic /lib/modules/

Update: Updates to WSL2 can break this as the directory name can change, e.g. 5.15.0-67-generic/ upgrades to 5.15.0-69-generic/. I created a script to automate this:


set -e

WSL2_VERSION=$(uname -r)

if [ -L "${WSL2_LINK}" ]; then
    if [ -e "${WSL2_LINK}" ]; then
        echo "Good link"
        exit 0
        echo "Broken link"
        rm "${WSL2_LINK}"
elif [ -e "${WSL2_LINK}" ]; then
    echo "Not a link"
    exit 1
    echo "Missing"

shopt -s nullglob
for filename in /lib/modules/*; do
    echo "$filename"
    if [ -z "$HEADERS_DIR" ]; then
        echo "HEADERS_DIR already set to $HEADERS_DIR, fail"
        exit 1

if [ -n "$HEADERS_DIR" ]; then
    echo "Create symbolic link $WSL2_LINK => $HEADERS_DIR"
    ln -s "$HEADERS_DIR" "$WSL2_LINK"
  • Just found this can break when WSL updates as the directory name can change, e.g. 5.15.0-67-generic/ upgrades to 5.15.0-69-generic/
    – parsley72
    Commented Apr 6, 2023 at 2:47

WLS2 does not have a Linux Kernel.

there are no kernel headers for WSL's "kernel" (actually a Windows driver)


You might be able to get away with compiling still by getting another kernel

# If asked to install grub, you can simply skip using Esc
sudo update && sudo apt install linux-generic -y

ls /lib/modules

You will probably have different output, but you should have kernel headers installed. You can modify your original command using whatever you get in lib/modules

make -C /lib/modules/<kernel source from previous step>/build M=$(PWD) modules
  • 5
    Thank you very much for your answer! I believe the biggest difference from WSL and WSL2 is that the latest has a real kernel: github.com/microsoft/WSL2-Linux-Kernel. I still don't know how to get the headers, I am too new to this :) Commented Jun 23, 2020 at 9:00
  • linux-generic : Depends: linux-headers-generic (= but is to be installed
    – Frank Fang
    Commented Nov 22, 2021 at 12:21
  • 4
    A bit late here, but @CarlosGarcia was absolutely right. This answer is simply not correct, and the "source" that was cited was specifically for WSL1 (in 2017, WSL2 wasn't even in beta). WSL2 does have a real Linux kernel. Commented Jan 3, 2022 at 19:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .