Ansible for Windows Servers: WinRM and SSH Setup Step by Step

Ansible for Windows servers: step-by-step WinRM and SSH connection setup with inventory examples

Ansible manages Windows servers without an agent, but the connection has to be prepared first: either a WinRM listener (the classic way) or the built-in OpenSSH Server (officially supported since ansible-core 2.18). The short answer: in an Active Directory domain, use WinRM with Kerberos over HTTP; for standalone servers and local accounts, use WinRM over HTTPS, or SSH with key authentication if your hosts run Windows Server 2022 or newer. Both setups follow below with the commands from the official documentation. Facts were checked against docs.ansible.com and Microsoft Learn on 28 October 2026. Not tested yet: we have not run these steps on our own lab VMs, so there are no timing or performance claims here.

WinRM or SSH: a quick comparison

PointWinRM (psrp / winrm plugins)SSH (ssh plugin)
Windows versionsWindows Server 2016 / Windows 10 and newerOfficially Windows Server 2022 and newer (needs the in-box OpenSSH 7.9.0.0 or later; Server 2019 ships 7.7.2.1)
Ports5985 (HTTP), 5986 (HTTPS)22
Extra Python packages on the control nodepywinrm or pypsrp (installed separately)None; sshpass only for password login
AuthenticationBasic, Certificate, NTLM, Kerberos, CredSSP (psrp also offers Negotiate)Key, GSSAPI (Kerberos), password
Strengths named in the docsWorks on every supported Windows version; psrp is slightly faster and copes better with loaded hosts and proxiesFaster file transfers; easier to set up outside a domain
Main caveatCredentials are not delegated for most auth types (the “double hop” problem)Key auth has the same double-hop limit; Kerberos ticket must exist before the run

What you need before you start

  • A Linux control node. Ansible cannot run on Windows as the control node; the docs allow WSL or a container but say WSL is not for production, so a small Linux VM is the usual answer.
  • Supported Windows hosts. Windows Server 2016 / Windows 10 or newer with the default PowerShell 5.1. No bootstrap is required on the Windows side.
  • An administrator account on each host. Modules such as Windows Update need a member of the local Administrators group.
  • The Windows collections. The community ansible package already bundles ansible.windows, community.windows, microsoft.ad and chocolatey.chocolatey. With bare ansible-core, install them with ansible-galaxy collection install ansible.windows and so on.

Option 1: WinRM setup step by step

Step 1: Install the WinRM client on the control node

The psrp and winrm connection plugins need Python libraries that are not part of the Ansible package. According to the Windows Remote Management guide, install one of them next to Ansible:

# psrp plugin (recommended by the docs: Negotiate auth, better under load)
pipx inject ansible "pypsrp<=1.0.0"

# or the classic winrm plugin
pipx inject ansible "pywinrm>=0.4.0"

With plain pip, the equivalents are pip3 install "pypsrp<=1.0.0" and pip3 install "pywinrm>=0.4.0". For Kerberos, add the [kerberos] extra and the system packages listed in the Kerberos section below.

Step 2: Enable the listener on each Windows host

WinRM has been enabled by default on Windows Server since 2012, but the documentation still recommends checking the listener and firewall. Run this in an elevated PowerShell on the server. It enables the service and HTTP listener, opens port 5985 and lets local accounts be used remotely:

# Enables the WinRM service and sets up the HTTP listener
Enable-PSRemoting -Force

# Opens port 5985 for all profiles
$firewallParams = @{
    Action      = 'Allow'
    Description = 'Inbound rule for Windows Remote Management via WS-Management. [TCP 5985]'
    Direction   = 'Inbound'
    DisplayName = 'Windows Remote Management (HTTP-In)'
    LocalPort   = 5985
    Profile     = 'Any'
    Protocol    = 'TCP'
}
New-NetFirewallRule @firewallParams

# Allows local user accounts to be used with WinRM
# This can be ignored if using domain accounts
$tokenFilterParams = @{
    Path         = 'HKLM:SOFTWAREMicrosoftWindowsCurrentVersionPoliciesSystem'
    Name         = 'LocalAccountTokenFilterPolicy'
    Value        = 1
    PropertyType = 'DWORD'
    Force        = $true
}
New-ItemProperty @tokenFilterParams

Per Microsoft, Enable-PSRemoting starts the WinRM service, sets it to automatic, creates a listener on all addresses and enables the firewall exception; on client editions on a public network profile it needs -SkipNetworkProfileCheck. The Ansible docs warn that these demonstration scripts open the firewall to every address and enable local accounts, so tighten them for production. Check the result with:

winrm enumerate winrm/config/Listener

Step 3 (recommended outside a domain): add an HTTPS listener

The documentation is explicit that Basic and NTLM should not be used over an HTTP listener. For standalone servers, create a certificate and an HTTPS listener on port 5986:

# Create self signed certificate
$certParams = @{
    CertStoreLocation = 'Cert:LocalMachineMy'
    DnsName           = $env:COMPUTERNAME
    NotAfter          = (Get-Date).AddYears(1)
    Provider          = 'Microsoft Software Key Storage Provider'
    Subject           = "CN=$env:COMPUTERNAME"
}
$cert = New-SelfSignedCertificate @certParams

# Create HTTPS listener
$httpsParams = @{
    Path                  = 'WSMan:localhostListener'
    Address               = '*'
    CertificateThumbprint = $cert.Thumbprint
    Enabled               = $true
    Port                  = 5986
    Transport             = 'HTTPS'
    Force                 = $true
}
New-Item @httpsParams

# Opens port 5986 for all profiles
$firewallParams = @{
    Action      = 'Allow'
    Description = 'Inbound rule for Windows Remote Management via WS-Management. [TCP 5986]'
    Direction   = 'Inbound'
    DisplayName = 'Windows Remote Management (HTTPS-In)'
    LocalPort   = 5986
    Profile     = 'Any'
    Protocol    = 'TCP'
}
New-NetFirewallRule @firewallParams

Prefer a certificate from your own CA: a self-signed one forces you to disable validation on the control node.

Step 4: Inventory and first connection

Group the Windows hosts and set the connection variables. This example uses the psrp plugin, NTLM with a local administrator, and the HTTPS listener from step 3:

[windows]
srv-app01.example.local
srv-app02.example.local

[windows:vars]
ansible_connection=psrp
ansible_user=ansible-admin
ansible_psrp_auth=ntlm
ansible_psrp_cert_validation=ignore

For the winrm plugin, the names are ansible_winrm_transport and ansible_winrm_server_cert_validation. Put the password in a file encrypted with Ansible Vault rather than in the inventory, then test:

ansible windows -i inventory.ini -m ansible.windows.win_ping --ask-pass

Kerberos in a domain

In Active Directory the recommended path is Kerberos over HTTP: traffic is encrypted at the message level and credentials can be delegated. On the control node install the Kerberos libraries and the Python extra, for example on Debian or Ubuntu:

sudo apt-get install krb5-user libkrb5-dev python3-dev
pipx inject ansible "pypsrp[kerberos]<=1.0.0"

Point /etc/krb5.conf at the domain (a DNS-based minimum is default_realm plus dns_lookup_kdc = true), get a ticket with kinit username@MY.DOMAIN.COM and verify with klist. The Kerberos guide notes that the psrp and winrm plugins can also obtain the ticket themselves when the password is supplied in the inventory, that the control node’s clock must be in sync with the domain controller, and that the server needs an SPN. With psrp, set ansible_psrp_auth=negotiate so Kerberos is tried first with NTLM as fallback. If a task must reach another server (a file share, SQL Server), enable delegation with ansible_psrp_negotiate_delegate=true.

Option 2: SSH setup step by step

The Windows SSH guide supports only Microsoft’s in-box OpenSSH, not the separate Win32-OpenSSH package, and requires OpenSSH 7.9.0.0 or later. That is why official support starts with Windows Server 2022; Microsoft states that on Windows Server 2025 OpenSSH is installed by default and can be switched on from Server Manager.

Step 1: Install and start OpenSSH Server

In an elevated PowerShell on the Windows host:

# Install the OpenSSH Server
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

# Start the service and make it automatic
Set-Service -Name sshd -StartupType Automatic -Status Running

# Make sure the firewall rule exists (setup normally creates it)
if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue)) {
    New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
}

Step 2: Set PowerShell as the default shell

Ansible works with the default cmd.exe shell, but the documentation recommends PowerShell. The change applies to the next SSH connection, so sshd does not need a restart:

$shellParams = @{
    Path         = 'HKLM:SOFTWAREOpenSSH'
    Name         = 'DefaultShell'
    Value        = 'C:WindowsSystem32WindowsPowerShellv1.0powershell.exe'
    PropertyType = 'String'
    Force        = $true
}
New-ItemProperty @shellParams

Step 3: Keys for administrators

Public keys of users in the Administrators group do not go into the user profile. They are read from C:ProgramDatasshadministrators_authorized_keys, as defined by the Match Group administrators block in C:ProgramDatasshsshd_config. Add the control node’s public key there. Domain users can instead authenticate with GSSAPI by setting GSSAPIAuthentication yes in sshd_config and restarting the service with Restart-Service -Name sshd.

Step 4: Inventory

[windows]
srv-app01.example.local

[windows:vars]
ansible_connection=ssh
ansible_shell_type=powershell
ansible_user=ansible-admin

ansible_shell_type must match the shell configured on the host: powershell after step 2, cmd otherwise. Then run the same win_ping test as above, this time without --ask-pass.

First real playbook: updates and software

Once win_ping answers, a typical first job is patching. The ansible.windows.win_updates module installs Critical, Security and Update Rollup categories by default, uses whatever update source the machine already has (Windows Update or WSUS), does not reboot unless told to, and must run as a local administrator. A conservative version:

- name: Patch Windows servers
  hosts: windows
  tasks:
    - name: Install security and critical updates
      ansible.windows.win_updates:
        category_names:
          - SecurityUpdates
          - CriticalUpdates
        reboot: true
        log_path: C:ansible_updates.log

    - name: Install tools with Chocolatey
      chocolatey.chocolatey.win_chocolatey:
        name:
          - git
          - 7zip
        state: present

Two notes from the module documentation: over SSH, Windows Update can restart the network adapter, so the docs recommend ServerAliveInterval=30 and disabling ControlMaster in ansible_ssh_args; and async does not work together with reboot: true. The win_chocolatey module installs Chocolatey itself if it is missing, so no manual bootstrap is needed. Run everything against one host first with --limit srv-app01* and with --check where the module supports it.

Limitations

  • Not tested yet. Every command above comes from the official documentation; we have not executed the sequence on our own VMs. Expect differences in hardened environments with Group Policy controlling WinRM or the firewall.
  • Double hop. With NTLM, Basic, Certificate or SSH keys, the remote session cannot pass your credentials on to a third server. Use Kerberos with delegation, CredSSP (the docs say only when absolutely necessary), or become with explicit credentials.
  • WinRM session limits. Many Windows Update API calls are blocked over WinRM, commands run non-interactively, and processes that rely on DPAPI (some SQL Server installers) fail. win_updates works around this with a scheduled task or become.
  • SSH on older servers. Windows Server 2019 and earlier may work with SSH but are not supported; use WinRM there.

For the rest of the toolchain, see our DevOps automation tools hub. Windows test targets are easy to spin up with Vagrant or on Proxmox VE; see the virtualization and containers hub.

FAQ

Ansible Windows: WinRM or SSH?

WinRM works on every supported Windows version and is the mature path, especially with Kerberos in a domain. SSH is officially supported from ansible-core 2.18 on Windows Server 2022 and newer, transfers files faster and is simpler outside a domain. Pick WinRM if you have older servers or rely on Kerberos tickets obtained from a password; pick SSH for new standalone hosts with key authentication.

Which ports does Ansible use for Windows?

WinRM uses TCP 5985 for HTTP and 5986 for HTTPS. SSH uses TCP 22. The listener and the matching firewall rule must both be present on the host.

How do I enable WinRM for Ansible?

Run Enable-PSRemoting -Force in an elevated PowerShell, confirm the firewall rule for port 5985, and set LocalAccountTokenFilterPolicy if you use local accounts. For production outside a domain, add an HTTPS listener on 5986 with a proper certificate.

What is the difference between the psrp and winrm connection plugins?

Both run over WinRM. psrp is PowerShell Remoting, offers Negotiate authentication (Kerberos with NTLM fallback) and, according to the docs, can be slightly faster and more tolerant of loaded hosts and proxies. winrm is the older plugin and must be told kerberos or ntlm explicitly.

Does Ansible need an agent on Windows?

No. It needs only PowerShell 5.1 (shipped with supported Windows versions) and an enabled WinRM listener or OpenSSH Server.

Can Ansible install Windows updates?

Yes, with ansible.windows.win_updates. It searches, downloads and installs by category, can reboot as many times as needed when reboot: true is set, and reports reboot_required otherwise. It does not support SCCM-managed clients.

Last updated: 28 October 2026 · ITForgePro editorial team. Licence, version and platform details are checked against each developer's official documentation.

Other articles

Submit your application