How to Use Ansible WinRM to Manage Windows Server 2022

Windows Remote Management (WinRM) is the Microsoft implementation of the WS-Management protocol — a SOAP-based protocol for managing hardware and software over a network. Ansible uses WinRM as its transport layer to communicate with Windows hosts in the same way it uses SSH for Linux. Understanding WinRM deeply is essential for reliable, secure, and performant Ansible automation against Windows Server 2022 environments. This guide covers every aspect of the WinRM-Ansible integration, from initial configuration to Kerberos authentication and performance tuning.

WinRM Configuration with ConfigureRemotingForAnsible.ps1

The quickest way to prepare a Windows Server 2022 host for Ansible is with Microsoft’s official configuration script. Download and execute it in an elevated PowerShell session on the target Windows host:

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$url = "https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
$file = "$env:TEMPConfigureRemotingForAnsible.ps1"
(New-Object -TypeName System.Net.WebClient).DownloadFile($url, $file)
powershell.exe -ExecutionPolicy ByPass -File $file -Verbose

The script takes optional parameters to control what it configures. To force enabling HTTPS only and skip HTTP:

powershell.exe -ExecutionPolicy ByPass -File $file -EnableCredSSP -ForceNewSSLCert -SkipNetworkProfileCheck

If you prefer to configure WinRM manually without the script, the equivalent commands are:

# Start and configure WinRM service
winrm quickconfig -force
winrm set winrm/config/service '@{AllowUnencrypted="false"}'
winrm set winrm/config/service/auth '@{Basic="true"; NTLM="true"}'
winrm set winrm/config '@{MaxTimeoutms="1800000"}'
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'

# Create HTTPS listener (requires a certificate thumbprint)
$cert = New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:LocalMachineMy
winrm create winrm/config/Listener?Address=*+Transport=HTTPS "@{Hostname=""$env:COMPUTERNAME"";CertificateThumbprint=""$($cert.Thumbprint)""}"

# Open Windows Firewall for WinRM HTTPS
netsh advfirewall firewall add rule name="WinRM HTTPS" protocol=TCP dir=in localport=5986 action=allow

HTTPS vs HTTP WinRM for Ansible

WinRM supports two transport modes: HTTP on port 5985 and HTTPS on port 5986. HTTP transmits data unencrypted unless NTLM or Kerberos authentication is used (which encrypts the session at the message level). HTTPS encrypts all traffic at the transport layer using TLS, providing defense-in-depth regardless of the authentication method used.

For any production environment, use HTTPS exclusively. In your Ansible inventory, configure the HTTPS transport:

# inventory/group_vars/windows.yml
ansible_port: 5986
ansible_connection: winrm
ansible_winrm_scheme: https
ansible_winrm_transport: ntlm
ansible_winrm_server_cert_validation: ignore

To disable the HTTP listener on Windows hosts after confirming HTTPS works, run this on each Windows host:

winrm delete winrm/config/Listener?Address=*+Transport=HTTP

Verify only the HTTPS listener remains:

winrm enumerate winrm/config/listener

NTLM Authentication for Ansible WinRM

NTLM is the simplest authentication method for Ansible-WinRM and works without Active Directory. It supports both local Windows accounts and domain accounts. Configure it in inventory:

ansible_winrm_transport: ntlm
ansible_user: DOMAINansible_svc
ansible_password: "{{ vault_windows_password }}"

For local accounts, use just the username without a domain prefix. NTLM over HTTPS provides both encryption and authentication suitable for most automated tasks. The pywinrm library handles NTLM negotiation automatically when the transport is set to ntlm.

Kerberos Authentication for Ansible

Kerberos authentication is the recommended method for domain-joined Windows hosts in an Active Directory environment. It is more secure than NTLM and does not require transmitting password hashes. Kerberos requires the Ansible control node to be configured as a Kerberos client.

On the Linux control node, install the Kerberos client libraries and the Python Kerberos package:

# Ubuntu/Debian
sudo apt install -y krb5-user krb5-config libkrb5-dev python3-dev gcc
pip install pywinrm[kerberos] requests-kerberos

Configure /etc/krb5.conf on the Linux control node with your Active Directory domain details:

[libdefaults]
    default_realm = YOURDOMAIN.COM
    dns_lookup_realm = false
    dns_lookup_kdc = true
    forwardable = true

[realms]
    YOURDOMAIN.COM = {
        kdc = dc01.yourdomain.com
        admin_server = dc01.yourdomain.com
    }

[domain_realm]
    .yourdomain.com = YOURDOMAIN.COM
    yourdomain.com = YOURDOMAIN.COM

Obtain a Kerberos ticket on the control node before running Ansible:

kinit [email protected]
klist  # Verify ticket was obtained

Configure inventory to use Kerberos:

ansible_winrm_transport: kerberos
ansible_user: [email protected]
ansible_port: 5985   # Kerberos typically runs over HTTP since it encrypts at message level

Kerberos requires that Windows hostnames in your inventory match the hostnames registered in DNS and Active Directory — it uses Service Principal Names (SPNs) for authentication. IP addresses will not work with Kerberos.

CredSSP Authentication

CredSSP (Credential Security Support Provider) allows the Ansible-supplied credentials to be delegated to the Windows host, enabling the remote session to authenticate to other network resources (such as file shares or databases). This is needed when your Ansible tasks must access network resources while running on the target host — the “double hop” problem.

Enable CredSSP on the Windows host:

Enable-WSManCredSSP -Role Server -Force

On the Linux control node, install the CredSSP library:

pip install pywinrm[credssp] requests-credssp

Configure inventory for CredSSP:

ansible_winrm_transport: credssp
ansible_winrm_server_cert_validation: ignore
ansible_user: DOMAINansible_svc
ansible_password: "{{ vault_windows_password }}"

Use CredSSP only when the double-hop capability is actually required. It transmits credentials to the remote host, which carries higher security risk than NTLM or Kerberos.

Inventory Variables for WinRM Transport

Ansible WinRM behavior is controlled through inventory variables. The full set of relevant variables for Windows host configuration:

# Connection type - always winrm for Windows
ansible_connection: winrm

# Protocol: http or https
ansible_winrm_scheme: https

# Authentication: basic, ntlm, kerberos, credssp, certificate
ansible_winrm_transport: ntlm

# Port: 5985 for HTTP, 5986 for HTTPS
ansible_port: 5986

# Certificate validation: validate or ignore
ansible_winrm_server_cert_validation: ignore

# Timeout in seconds for WinRM operations
ansible_winrm_operation_timeout_sec: 60

# Total read timeout (must be > operation timeout)
ansible_winrm_read_timeout_sec: 70

# Path to CA certificate for validation
ansible_winrm_ca_trust_path: /etc/ssl/certs/internal-ca.pem

# Message encryption (auto, always, never)
ansible_winrm_message_encryption: auto

Testing with win_ping

The win_ping module is the standard connectivity test for Ansible Windows hosts. It does not use ICMP ping — it establishes a WinRM connection, executes a small PowerShell command on the host, and returns the result. Test all hosts in the windows group:

ansible windows -i inventory/windows.yml -m win_ping

Test a single specific host:

ansible ws2022-01 -i inventory/windows.yml -m win_ping

Test with verbose output to see WinRM negotiation details:

ansible windows -i inventory/windows.yml -m win_ping -vvvv 2>&1 | head -100

Troubleshooting WinRM Authentication Errors

The most common WinRM errors and their solutions:

Error: “Basic auth over HTTP is not supported.” This means WinRM on the Windows host has AllowUnencrypted set to false. Either use HTTPS, or temporarily allow unencrypted connections for testing (not recommended for production):

# On the Windows host - only for testing
winrm set winrm/config/service '@{AllowUnencrypted="true"}'

Error: “401 Unauthorized.” Check the username, password, and winrm_transport. For NTLM, ensure the user account has permissions on the Windows host. Verify with:

# Test WinRM authentication directly from the Linux control node using curl
curl -u 'DOMAIN\ansible_svc:password' 
  --ntlm 
  -k 
  "https://ws2022-01:5986/wsman" 
  -d '' 
  -H "Content-Type: application/soap+xml;charset=UTF-8"

Error: “WinRM HTTP 500.” Usually indicates the WinRM service has run out of shells or memory. Increase the MaxConcurrentUsers and MaxShellsPerUser limits on the Windows host:

winrm set winrm/config/winrs '@{MaxConcurrentUsers="20"}'
winrm set winrm/config/winrs '@{MaxShellsPerUser="50"}'
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="2048"}'

Error: “Connection refused on port 5986.” The HTTPS listener may not be created. Verify and recreate it:

# On the Windows host
winrm enumerate winrm/config/listener
# If HTTPS listener is missing, recreate it:
$cert = Get-ChildItem Cert:LocalMachineMy | Where-Object {$_.Subject -match $env:COMPUTERNAME} | Select-Object -First 1
If (-not $cert) {
    $cert = New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:LocalMachineMy
}
winrm create winrm/config/Listener?Address=*+Transport=HTTPS `
  "@{Hostname=""$env:COMPUTERNAME"";CertificateThumbprint=""$($cert.Thumbprint)""}"

Handling Self-Signed Certificates

The ConfigureRemotingForAnsible.ps1 script creates a self-signed certificate for the HTTPS WinRM listener. By default, Python’s certificate validation rejects self-signed certs. In Ansible, set ansible_winrm_server_cert_validation: ignore to bypass validation. This is acceptable for internal networks but not ideal.

For a proper solution, issue certificates from your internal Certificate Authority. Export the CA certificate as a PEM file and configure Ansible to use it for validation:

# Export internal CA cert from Windows (on the CA server or any domain-joined machine)
$cert = Get-ChildItem Cert:LocalMachineRoot | Where-Object {$_.Subject -match "Internal CA"}
$cert | Export-Certificate -FilePath C:Tempinternal-ca.cer -Type CERT
# Copy to Linux control node and convert to PEM
openssl x509 -inform DER -in internal-ca.cer -out internal-ca.pem

Then in inventory:

ansible_winrm_server_cert_validation: validate
ansible_winrm_ca_trust_path: /etc/ansible/certs/internal-ca.pem

Windows Vault for Credentials

Never store Windows credentials in plaintext inventory files. Use Ansible Vault to encrypt sensitive variables:

# Create an encrypted vault file
ansible-vault create inventory/group_vars/windows/vault.yml

Add the following to the vault file (Ansible Vault will encrypt it):

vault_windows_password: "YourSecurePassword123!"

Reference the vault variable in the non-encrypted group_vars file:

# inventory/group_vars/windows/vars.yml
ansible_password: "{{ vault_windows_password }}"

Run playbooks with vault decryption:

ansible-playbook -i inventory/ playbooks/configure_windows.yml --ask-vault-pass
# Or use a vault password file (store it securely, not in git)
ansible-playbook -i inventory/ playbooks/configure_windows.yml --vault-password-file ~/.vault_pass

Performance Tuning Ansible for Windows

Windows WinRM connections have higher overhead than SSH. Each task creates a new WinRM shell session by default. To reduce connection overhead, increase the number of parallel forks in ansible.cfg and optimize WinRM settings on the Windows hosts:

# ansible.cfg - increase parallelism
forks = 20
pipelining = False  # Pipelining does not work with WinRM; keep False

Increase WinRM memory and timeout limits on Windows hosts to handle complex playbooks without timeout errors:

winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="2048"}'
winrm set winrm/config '@{MaxTimeoutms="1800000"}'
winrm set winrm/config/winrs '@{MaxProcessesPerShell="100"}'

For playbooks with many sequential tasks, consider using the ansible.windows.win_shell module with multi-line scripts instead of separate tasks for each operation. This reduces the number of WinRM round trips significantly:

- name: Perform multiple configuration steps in one WinRM call
  ansible.windows.win_shell: |
    Set-TimeZone -Id "UTC"
    Set-ItemProperty -Path "HKLM:SYSTEMCurrentControlSetControlSession ManagerPower" -Name HiberbootEnabled -Value 0
    Disable-ScheduledTask -TaskName "MicrosoftWindowsUpdateOrchestratorReboot"
    Set-Service -Name "SysMain" -StartupType Disabled
    Stop-Service -Name "SysMain" -Force
  register: config_result

For large-scale Windows fleet management (hundreds of servers), consider running multiple Ansible control nodes and splitting inventory by geographic region or application tier. The Windows WinRM protocol is inherently more resource-intensive than SSH, and parallelism is your most effective tool for maintaining reasonable playbook execution times at scale.