Managing WSL instances directly with Ansible

Hi,

Recently I published a certified collection that supports WSL automation via Ansible, microsoft.wsl. I put together a brief demo of how the modules can support direct management of WSL instances by setting up an SSH server.

For transparency, I did use AI to help compile this post.

From Windows Host to Linux SSH Server in a Single Playbook

WSL distributions are useful, but they live in a silo. You can install one and run commands inside it, but from Ansible’s perspective it’s a black box sitting behind a Windows host. If you want to manage that Linux environment the same way you manage any other server, you need SSH access.

The microsoft.wsl Ansible certified collection makes this straightforward. In a single playbook, you can install a distribution, configure it as an SSH server, and then connect to it directly with Ansible to manage it like any other Linux host.

Setting up the SSH server

The first play targets the Windows hosts over WinRM. It installs FedoraLinux-44 on WSL v2, enables systemd (required for service management), and then uses microsoft.wsl.command to install and start the SSH server inside the distribution.

---
- name: Configure WSL Fedora as an SSH Server
  hosts: windows
  gather_facts: false

  vars:
    distro_name: FedoraLinux-44
    ssh_port: 22
    wsl_user: root
    wsl_password: password

  tasks:
    - name: Install Fedora Linux 44 on WSL v2
      microsoft.wsl.distribution:
        name: "{{ distro_name }}"
        version: 2

    - name: Enable systemd and set hostname
      microsoft.wsl.distribution_config:
        name: "{{ distro_name }}"
        terminate: true
        boot:
          systemd: true
        network:
          hostname: fedora-wsl

    - name: Install openssh-server
      microsoft.wsl.command:
        name: "{{ distro_name }}"
        command: |
          dnf install -y openssh-server;
          ssh-keygen -A;
          sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config;
          systemctl enable sshd && systemctl restart sshd;
        user: root

The microsoft.wsl.distribution module handles installation and version conversion declaratively. If FedoraLinux-44 is already installed on v2, the task is a no-op. The microsoft.wsl.distribution_config module writes /etc/wsl.conf inside the distribution and optionally terminates it so the changes take effect on next start. With systemd enabled, we can use systemctl inside the distribution to manage sshd like we would on any Fedora server.

Bridging the network gap

WSL 2 distributions sit behind a virtual network. The SSH server is listening, but it’s only reachable at an internal IP that nothing outside the Windows host can see. To make it accessible, we set up port forwarding with netsh interface portproxy so traffic arriving on the Windows host’s port 22 is forwarded into the distribution.

    - name: Get the WSL distro IP address
      microsoft.wsl.command:
        name: "{{ distro_name }}"
        command: ip -4 -o addr show eth0 | cut -d' ' -f7 | cut -d/ -f1
      register: distro_ip

    - name: Forward SSH port from Windows to WSL distro
      ansible.windows.win_shell: >-
        netsh interface portproxy add v4tov4
        listenport={{ ssh_port }}
        listenaddress=0.0.0.0
        connectport={{ ssh_port }}
        connectaddress={{ distro_ip.stdout | trim }}

With the port forward in place, SSH connections to the Windows host’s IP are transparently routed to the Fedora instance inside WSL.

Connecting directly with Ansible

This is where it gets interesting. The last task in the first play uses add_host to register the WSL distribution as a new dynamic host in the wsl group, using the Windows host’s IP and the SSH credentials.

    - name: Add WSL distro as a dynamic host
      ansible.builtin.add_host:
        name: "{{ inventory_hostname }}-{{ distro_name }}"
        groups: wsl
        ansible_host: "{{ ansible_host }}"
        ansible_user: "{{ wsl_user }}"
        ansible_password: "{{ wsl_password }}"
        ansible_port: "{{ ssh_port }}"

A second play in the same playbook then targets the wsl group. Ansible connects over SSH, gathers facts, and runs tasks against what is now a standard Linux host.

- name: Manage WSL instance directly
  gather_facts: true
  hosts: wsl
  tasks:
    - name: Ping
      ansible.builtin.ping: {}

    - name: Display OS
      ansible.builtin.debug:
        msg: "{{ ansible_facts['distribution'] }} - {{ ansible_facts['distribution_version'] }}"

The output confirms that Ansible is connected directly to the Fedora instance and gathering facts over SSH:

TASK [Display OS] ************************************************************
ok: [demo1-FedoraLinux-44] => {
    "msg": "Fedora - 44"
}

PLAY RECAP *******************************************************************
demo1                      : ok=7    changed=5    unreachable=0    failed=0
demo1-FedoraLinux-44       : ok=3    changed=0    unreachable=0    failed=0

The first line in the recap is the Windows host, managed over WinRM. The second is the WSL distribution running on that same machine, managed over SSH. Two different connection methods, two different operating systems, orchestrated in a single playbook run.

Why this matters

The microsoft.wsl.command module is useful for running one-off commands inside a distribution, but it has limits. It’s a remote execution mechanism, not a full connection plugin. You don’t get Ansible facts, you can’t use most modules directly, and you lose the declarative, module-driven workflow that makes Ansible powerful.

By standing up SSH inside the distribution, you unlock the full Ansible module library. Package management with dnf, file templates, user management, service configuration, roles from Galaxy, everything works the same as it would against a standalone Fedora server. The WSL distribution becomes a first-class managed host.

The setup play only needs to run once. After that, the WSL distribution can be included in your regular inventory and managed alongside your other Linux hosts.

Find more information on Automation Hub or in the GitHub repository.