How to Use Ansible WinRM to Manage Windows Server 2025

Ansible’s WinRM transport is the bridge between your Linux-based Ansible control node and Windows managed hosts. While the previous guide introduced WinRM basics with NTLM authentication, production environments often demand stronger security guarantees: encrypted credential delegation through CredSSP, Kerberos single sign-on in Active Directory domains, or fully encrypted HTTPS communication over port 5986. This guide takes a deep dive into each WinRM authentication method, walks through configuring CredSSP and Kerberos on both sides of the connection, and shows how to set up a proper HTTPS WinRM listener for production use.

Prerequisites

  • Ansible control node (Linux) with Python 3.9+ and pywinrm installed
  • Windows Server 2025 with PowerShell remoting already enabled
  • Domain membership (for Kerberos transport) or local admin credentials (for NTLM/CredSSP)
  • OpenSSL available on the Linux control node (for certificate generation)

Step 1: Install pywinrm and Optional Dependencies

The pywinrm Python library is Ansible’s WinRM client. Different authentication transports require additional Python packages:

# Activate your Ansible virtual environment
source ~/ansible-env/bin/activate

# Core library (supports NTLM by default)
pip install pywinrm

# NTLM (included with pywinrm, but explicitly ensure it)
pip install requests-ntlm

# CredSSP transport
pip install pywinrm[credssp]
# Installs: requests-credssp, cryptography

# Kerberos transport
pip install pywinrm[kerberos]
# On RHEL/Rocky
sudo dnf install -y krb5-workstation krb5-libs python3-devel gcc libkrb5-dev
pip install requests-kerberos

# Certificate-based authentication
pip install pywinrm[kerberos]   # same package handles cert auth

# Verify all transports are importable
python3 -c "
import winrm
from winrm.protocol import Protocol
print('pywinrm version:', winrm.__version__)
"

Step 2: WinRM Authentication Methods Overview

Ansible supports four WinRM authentication transports, each with different trade-offs:

  • NTLM — works with local accounts, no Active Directory needed, encrypted challenge-response, moderate security. Best for non-domain environments or quick setup.
  • Kerberos — requires Active Directory. Provides mutual authentication, SSO, and no password transmission. Best for domain-joined servers in production.
  • CredSSP — delegates full credentials to the remote machine (needed for double-hop scenarios, e.g., when the Windows host must authenticate to a third system). Higher security risk if the managed host is compromised.
  • Certificate — uses client TLS certificates. No passwords involved. Requires PKI infrastructure.

Step 3: Configure CredSSP on Windows Server 2025

CredSSP (Credential Security Support Provider) is necessary when your Ansible playbooks need to make outbound authenticated connections from the managed Windows host — for example, accessing network shares or SQL Server with Windows Authentication during a deployment task.

On the Windows managed host, run these commands in an elevated PowerShell session:

# Enable CredSSP server-side role
Enable-WSManCredSSP -Role Server -Force

# Verify it is enabled
Get-WSManCredSSP

# Confirm the CredSSP service authentication setting
Get-Item WSMan:localhostServiceAuthCredSSP

# If the above returns $false, enable it explicitly
Set-Item -Path WSMan:localhostServiceAuthCredSSP -Value $true

# Optional: allow fresh credentials from the Ansible control node
# (needed when the control node itself uses CredSSP to a middle tier)
$policy = "WSMAN/ansible-control-node.example.com"
Set-Item -Path WSMan:localhostClientAuthCredSSP -Value $true
New-Item -Path "WSMan:localhostClientTrustedHosts" -Value "$policy" -Force

On some Windows Server 2025 configurations, Group Policy may block CredSSP delegation. Apply the required policy via PowerShell:

# Configure Group Policy to allow CredSSP delegation
# (Applies to Computer Configuration → Admin Templates → System → Credentials Delegation)
$regPath = "HKLM:SOFTWAREPoliciesMicrosoftWindowsCredentialsDelegation"
New-Item -Path $regPath -Force | Out-Null
Set-ItemProperty -Path $regPath -Name "AllowFreshCredentials" -Value 1 -Type DWord
Set-ItemProperty -Path $regPath -Name "ConcatenateDefaults_AllowFresh" -Value 1 -Type DWord

$listPath = "$regPathAllowFreshCredentials"
New-Item -Path $listPath -Force | Out-Null
Set-ItemProperty -Path $listPath -Name "1" -Value "WSMAN/*" -Type String

# Apply immediately
gpupdate /force

On the Ansible control node, update your inventory to use CredSSP:

# inventory/windows_credssp.yml
all:
  children:
    windows:
      hosts:
        win-srv-01:
          ansible_host: 192.168.10.20
      vars:
        ansible_user: Administrator
        ansible_password: "{{ vault_win_password }}"
        ansible_connection: winrm
        ansible_winrm_transport: credssp
        ansible_winrm_port: 5985
        ansible_winrm_server_cert_validation: ignore
        ansible_winrm_credssp_disable_tlsv1_2: false

Step 4: Configure Kerberos for Ansible in an Active Directory Domain

Kerberos is the preferred transport for domain-joined Windows Server 2025 targets. No passwords travel over the network — authentication uses encrypted tickets issued by the domain controller.

On the Linux control node, configure the Kerberos realm. Edit /etc/krb5.conf:

[libdefaults]
    default_realm = EXAMPLE.COM
    dns_lookup_realm = false
    dns_lookup_kdc = true
    ticket_lifetime = 24h
    renew_lifetime = 7d
    forwardable = true

[realms]
    EXAMPLE.COM = {
        kdc = dc01.example.com
        admin_server = dc01.example.com
        default_domain = example.com
    }

[domain_realm]
    .example.com = EXAMPLE.COM
    example.com = EXAMPLE.COM

Obtain a Kerberos ticket on the control node:

# Authenticate to the domain
kinit [email protected]

# Verify the ticket
klist

# Expected output:
# Credentials cache: FILE:/tmp/krb5cc_1000
# Principal: [email protected]
# Issued          Expires         Principal
# May 17 10:00:00 May 18 10:00:00 krbtgt/[email protected]

Configure the Ansible inventory for Kerberos:

all:
  children:
    domain_windows:
      hosts:
        win-srv-01.example.com:
          ansible_host: 192.168.10.20
        win-srv-02.example.com:
          ansible_host: 192.168.10.21
      vars:
        ansible_user: [email protected]
        ansible_connection: winrm
        ansible_winrm_transport: kerberos
        ansible_winrm_port: 5985
        ansible_winrm_kerberos_delegation: false
        ansible_winrm_server_cert_validation: ignore

Step 5: Set Up HTTPS WinRM (Port 5986)

For production environments, always use HTTPS WinRM on port 5986. This encrypts all traffic and validates the server’s identity. On the Windows managed host:

# Generate a self-signed certificate for WinRM HTTPS
# (Use a CA-signed cert in production)
$cert = New-SelfSignedCertificate `
    -DnsName $env:COMPUTERNAME, "win-srv-01.example.com" `
    -CertStoreLocation "Cert:LocalMachineMy" `
    -NotAfter (Get-Date).AddYears(5) `
    -KeyUsage KeyEncipherment, DataEncipherment `
    -Type SSLServerAuthentication

# Create the HTTPS WinRM listener using the certificate thumbprint
$thumbprint = $cert.Thumbprint
New-WSManInstance -ResourceURI winrm/config/Listener `
    -SelectorSet @{Transport="HTTPS"; Address="*"} `
    -ValueSet @{Hostname=$env:COMPUTERNAME; CertificateThumbprint=$thumbprint}

# Verify the HTTPS listener is active
winrm enumerate winrm/config/Listener

# Open HTTPS port in Windows Firewall
New-NetFirewallRule -DisplayName "WinRM HTTPS (5986)" `
    -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow

# Test HTTPS connectivity locally
$session = New-PSSession -ComputerName localhost -UseSSL `
    -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck)
Invoke-Command -Session $session -ScriptBlock { hostname }
Remove-PSSession $session

Export the certificate for trust on the control node:

# Export cert (without private key) for the control node
$certPath = "C:Tempwinrm-cert.cer"
Export-Certificate -Cert "Cert:LocalMachineMy$thumbprint" -FilePath $certPath
# Transfer the .cer file to the Linux control node via SCP

On the Linux control node, configure the inventory for HTTPS WinRM:

all:
  children:
    windows_https:
      hosts:
        win-srv-01:
          ansible_host: 192.168.10.20
      vars:
        ansible_user: Administrator
        ansible_password: "{{ vault_win_password }}"
        ansible_connection: winrm
        ansible_winrm_transport: ntlm
        ansible_winrm_port: 5986
        ansible_winrm_scheme: https
        # For self-signed certs in dev/test — remove in production
        ansible_winrm_server_cert_validation: ignore
        # For production with CA-signed cert:
        # ansible_winrm_server_cert_validation: validate
        # ansible_winrm_ca_trust_path: /etc/ssl/certs/winrm-ca.pem

Step 6: Testing with Ansible Ad-Hoc Commands

# Basic connectivity test
ansible windows -i inventory/windows.yml -m ansible.windows.win_ping

# Run a PowerShell command ad-hoc
ansible win-srv-01 -i inventory/windows.yml -m ansible.windows.win_shell 
    -a "Get-ComputerInfo | Select-Object WindowsProductName, TotalPhysicalMemory | ConvertTo-Json"

# Check disk space
ansible windows -i inventory/windows.yml -m ansible.windows.win_disk_facts

# Get installed hotfixes
ansible win-srv-01 -i inventory/windows.yml -m ansible.windows.win_shell 
    -a "Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 10 | ConvertTo-Json"

# Verbose connection debugging
ansible win-srv-01 -i inventory/windows.yml -m win_ping -vvvv 2>&1 | head -80

Step 7: Troubleshooting Common WinRM Issues

  • Connection refused on port 5985: Confirm WinRM is running with Get-Service WinRM and check the firewall rule allows inbound TCP 5985.
  • NTLM authentication failure: Verify the username format — use .LocalAdmin for local accounts or DOMAINuser for domain accounts. Do not prefix with the hostname.
  • CredSSP: The credentials were rejected: Ensure the Group Policy registry keys are set and gpupdate /force has been run. Also confirm requests-credssp is installed on the control node.
  • Kerberos: kinit failed: Check DNS resolves the domain controller FQDN. Kerberos is highly DNS-sensitive — ensure reverse lookups work.
  • WinRM timeout during long tasks: Increase timeout in the inventory: ansible_winrm_operation_timeout_sec: 120 and ansible_winrm_read_timeout_sec: 150.

Conclusion

Mastering Ansible’s WinRM transport layer is the key to reliable, secure automation of Windows Server 2025 infrastructure. NTLM is the quickest path to a working connection for standalone servers; Kerberos should be the standard for all domain-joined production hosts; CredSSP fills the gap when multi-hop credential delegation is required. By combining HTTPS WinRM listeners with certificate validation, Ansible Vault for credential storage, and Kerberos ticket-based authentication, you build an automation pipeline where no plaintext passwords traverse the network and every connection is encrypted end to end. With these foundations in place, scaling to hundreds of Windows Server 2025 nodes is simply a matter of expanding your inventory and refining your playbooks.