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
| Point | WinRM (psrp / winrm plugins) | SSH (ssh plugin) |
|---|---|---|
| Windows versions | Windows Server 2016 / Windows 10 and newer | Officially Windows Server 2022 and newer (needs the in-box OpenSSH 7.9.0.0 or later; Server 2019 ships 7.7.2.1) |
| Ports | 5985 (HTTP), 5986 (HTTPS) | 22 |
| Extra Python packages on the control node | pywinrm or pypsrp (installed separately) | None; sshpass only for password login |
| Authentication | Basic, Certificate, NTLM, Kerberos, CredSSP (psrp also offers Negotiate) | Key, GSSAPI (Kerberos), password |
| Strengths named in the docs | Works on every supported Windows version; psrp is slightly faster and copes better with loaded hosts and proxies | Faster file transfers; easier to set up outside a domain |
| Main caveat | Credentials 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
ansiblepackage already bundlesansible.windows,community.windows,microsoft.adandchocolatey.chocolatey. With bareansible-core, install them withansible-galaxy collection install ansible.windowsand 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 @tokenFilterParamsPer 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/ListenerStep 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 @firewallParamsPrefer 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=ignoreFor 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-passKerberos 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 @shellParamsStep 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-adminansible_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: presentTwo 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
becomewith 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_updatesworks around this with a scheduled task orbecome. - 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.





