Introduction: Ansible and Windows Server 2019

Ansible is an agentless automation platform that manages Windows Server 2019 nodes via Windows Remote Management (WinRM) rather than SSH. The Ansible control node (a Linux machine) pushes tasks to Windows targets over encrypted WinRM connections, running PowerShell under the covers. Properly configuring WinRM on Windows Server 2019 with appropriate authentication and encryption is the prerequisite for all Ansible-based Windows automation.

WinRM Architecture for Ansible

Ansible can use two WinRM listeners: HTTP (port 5985) and HTTPS (port 5986). For production use, HTTPS with certificate-based validation is required. Ansible supports four WinRM authentication providers: Basic, NTLM, Kerberos, and CredSSP. For domain-joined servers, Kerberos is the recommended choice as it provides mutual authentication and credential delegation without transmitting passwords; for non-domain systems, NTLM or certificate auth is used.

Quick Bootstrap Script for Development/Testing

Microsoft provides an official bootstrap script that configures WinRM for Ansible in one step. For production, do not run this script—follow the manual steps below instead:


# Download and run the official Ansible bootstrap script (dev/test only)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$url = 'https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1'
$file = "$env:TEMPConfigureRemotingForAnsible.ps1"
Invoke-WebRequest -Uri $url -OutFile $file
& $file -EnableCredSSP -DisableBasicAuth -Verbose

Manual WinRM Configuration for Production (HTTPS + Kerberos)


# Step 1: Enable and configure WinRM service
Enable-PSRemoting -Force
Set-Service WinRM -StartupType Automatic
Start-Service WinRM

# Step 2: Verify current listeners
winrm enumerate winrm/config/listener

# Step 3: Request a certificate from internal CA for this server
$certRequest = @{
    DnsName           = @($env:COMPUTERNAME, "$env:COMPUTERNAME.corp.local")
    CertStoreLocation = 'Cert:LocalMachineMy'
    KeyUsage          = 'KeyEncipherment', 'DigitalSignature'
    TextExtension     = @('2.5.29.37={text}1.3.6.1.5.5.7.3.1')  # Server Auth EKU
    NotAfter          = (Get-Date).AddYears(2)
}
$cert = New-SelfSignedCertificate @certRequest  # Use CA-issued cert in production
$thumbprint = $cert.Thumbprint

# Step 4: Create HTTPS WinRM listener
New-Item -Path WSMan:localhostListener -Transport HTTPS -Address * `
    -CertificateThumbPrint $thumbprint -Force

# Step 5: Open firewall for WinRM HTTPS only
Remove-NetFirewallRule -DisplayName 'Windows Remote Management (HTTP-In)' -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName 'WinRM HTTPS for Ansible' `
    -Direction Inbound -Protocol TCP -LocalPort 5986 `
    -RemoteAddress '10.0.0.0/8' -Action Allow -Profile Domain

# Step 6: Configure WinRM authentication
Set-Item WSMan:localhostServiceAuthBasic      $false
Set-Item WSMan:localhostServiceAuthKerberos   $true
Set-Item WSMan:localhostServiceAuthNegotiate  $true
Set-Item WSMan:localhostServiceAuthCertificate $false
Set-Item WSMan:localhostServiceAllowUnencrypted $false

# Step 7: Set WinRM limits appropriate for Ansible
Set-Item WSMan:localhostMaxTimeoutms 1800000
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'
winrm set winrm/config/winrs '@{MaxProcessesPerShell="25"}'
winrm set winrm/config/winrs '@{MaxShellsPerUser="30"}'

Configuring Kerberos Authentication for Domain Members

Kerberos is the preferred authentication for domain-joined Windows servers. The Ansible control node’s Linux host must have Kerberos configured to obtain tickets from the Active Directory KDC:


# On the Linux Ansible controller, install Kerberos tools
# Ubuntu/Debian:
# sudo apt-get install -y krb5-user python3-pip
# sudo pip3 install pywinrm[kerberos]

# /etc/krb5.conf on the Ansible controller:
# [libdefaults]
#     default_realm = CORP.LOCAL
#     dns_lookup_realm = false
#     dns_lookup_kdc = true
#
# [realms]
#     CORP.LOCAL = {
#         kdc = dc01.corp.local
#         admin_server = dc01.corp.local
#     }
#
# [domain_realm]
#     .corp.local = CORP.LOCAL
#     corp.local = CORP.LOCAL

# Test Kerberos ticket acquisition
# kinit [email protected]
# klist

# Verify WinRM connectivity from Ansible controller
# ansible windows_servers -m win_ping --extra-vars "[email protected] ansible_password=secret"

# On Windows Server 2019, verify Kerberos is configured correctly
# Check SPN registration for WinRM
setspn -L $env:COMPUTERNAME
setspn -Q WSMAN/$env:COMPUTERNAME

# Add missing SPN if needed
setspn -A WSMAN/$env:COMPUTERNAME.corp.local $env:COMPUTERNAME

Ansible Inventory and Variables for Windows

On the Ansible controller, configure the inventory file and group variables to use WinRM:


# inventory/windows_servers.yml
---
windows_servers:
  hosts:
    srv01.corp.local:
    srv02.corp.local:
    srv03.corp.local:
  vars:
    ansible_connection: winrm
    ansible_winrm_transport: kerberos
    ansible_winrm_scheme: https
    ansible_port: 5986
    ansible_winrm_server_cert_validation: validate  # use 'ignore' for self-signed only
    ansible_winrm_kerberos_delegation: false
    ansible_winrm_operation_timeout_sec: 120
    ansible_winrm_read_timeout_sec: 150

# group_vars/windows_servers.yml (optional host-specific overrides)
# ansible_user: [email protected]
# Note: use ansible-vault for the password:
# ansible-vault encrypt_string 'S3cretP@ssw0rd' --name ansible_password

Configuring CredSSP for Tasks Requiring Credential Delegation

Some tasks require credential delegation—for example, joining a domain from Ansible or accessing network shares. CredSSP allows the encrypted delegation of credentials to the managed node:


# Enable CredSSP on Windows Server 2019 target
Enable-WSManCredSSP -Role Server -Force

# Configure CredSSP client policy via GPO or registry:
$credSSPKey = 'HKLM:SOFTWAREPoliciesMicrosoftWindowsCredentialsDelegation'
New-Item -Path $credSSPKey -Force | Out-Null
Set-ItemProperty $credSSPKey -Name 'AllowFreshCredentials' -Value 1
Set-ItemProperty $credSSPKey -Name 'ConcatenateDefaults_AllowFresh' -Value 1

$allowKey = "$credSSPKeyAllowFreshCredentialsList"
New-Item -Path $allowKey -Force | Out-Null
Set-ItemProperty $allowKey -Name '1' -Value 'WSMAN/*.corp.local'

# On the Ansible controller
# group_vars/windows_servers.yml:
# ansible_winrm_transport: credssp
# ansible_winrm_credssp_disable_tlsv1_2: false

# Test connectivity with CredSSP
# ansible srv01.corp.local -m win_ping -e "ansible_winrm_transport=credssp"

Testing and Troubleshooting WinRM Connectivity


# From Windows Server 2019 target - verify listener and auth config
winrm get winrm/config/service/auth
winrm enumerate winrm/config/listener

# Test HTTPS listener is responding
Test-WSMan -ComputerName localhost -UseSSL

# From a remote management machine - test connectivity
Test-WSMan -ComputerName SRV01.corp.local -UseSSL -Authentication Kerberos `
    -Credential (Get-Credential)

# Check WinRM event log for authentication failures
Get-WinEvent -LogName 'Microsoft-Windows-WinRM/Operational' -MaxEvents 50 |
    Where-Object { $_.LevelDisplayName -eq 'Error' } |
    Select-Object TimeCreated, Id, Message

# Common issues and fixes:
# Error: "The target name used for Kerberos authentication is incorrect"
# Fix: Register the WSMAN SPN
setspn -A WSMAN/SRV01 SRV01$
setspn -A WSMAN/SRV01.corp.local SRV01$

# Error: "Access is denied" with Basic auth
# Fix: Ensure Basic auth is disabled and using Negotiate/Kerberos
Set-Item WSMan:localhostServiceAuthNegotiate $true

# Verify Ansible can reach the host
# ansible SRV01 -m win_command -a 'ipconfig /all'

GPO for Fleet-Wide WinRM Configuration


# Deploy WinRM configuration via GPO startup script or DSC
# GPO path: Computer Configuration > Preferences > Windows Settings > Registry

# Key registry values to set via GPO:
# HKLMSOFTWAREPoliciesMicrosoftWindowsWinRMServiceAllowBasic = 0
# HKLMSOFTWAREPoliciesMicrosoftWindowsWinRMServiceAllowUnencrypted = 0
# HKLMSOFTWAREPoliciesMicrosoftWindowsWinRMServiceAllowKerberos = 1

# GPO startup script to create HTTPS listener on all domain servers
# Computer Configuration > Windows Settings > Scripts > Startup
# Script: C:NETLOGONConfigureWinRM.ps1

$gpo = New-GPO -Name 'Configure WinRM for Ansible'
New-GPLink -Name 'Configure WinRM for Ansible' -Target 'OU=Servers,DC=corp,DC=local'

# Verify GPO application on target
gpresult /R /SCOPE COMPUTER | Select-String -Pattern 'WinRM'

Conclusion

Configuring WinRM on Windows Server 2019 for Ansible control requires deliberate choices about transport security and authentication. HTTPS listeners with Kerberos authentication on domain-joined servers provide strong mutual authentication without credential exposure. Fleet-wide deployment via GPO ensures consistent, auditable configuration across all managed nodes. With this foundation, Ansible can reliably manage Windows Server 2019 infrastructure using its extensive Windows module library for everything from patch management to complex application deployment.