Fix Ansible WinRM SSL Error: HTTPSConnectionPool Max Retries Exceeded on Port 5986

intermediate๐Ÿ”ง Ansible2026-05-11| Ansible 2.9+, Python 3.x, Windows Server 2016/2019/2022, WinRM over HTTPS (port 5986)

Error Message

fatal: [win_host]: UNREACHABLE! => {"msg": "ssl: HTTPSConnectionPool(host='192.168.1.10', port=5986): Max retries exceeded with url: /wsman"}
#ansible#winrm#windows#ssl#connection#https

What triggers this error

You run an Ansible playbook targeting a Windows host and get slapped with:

fatal: [win_host]: UNREACHABLE! => {"msg": "ssl: HTTPSConnectionPool(host='192.168.1.10', port=5986): Max retries exceeded with url: /wsman"}

The connection never even got off the ground. Three things typically cause this: the port isn't open, the SSL handshake blew up, or Ansible doesn't trust the certificate. Pick the one that fits your situation and work through the steps below.

Root causes

  • WinRM HTTPS listener not configured on the Windows host
  • Port 5986 blocked by Windows Firewall or an upstream network firewall
  • Self-signed certificate not trusted by Ansible (missing validate_certs: false)
  • pywinrm package missing or outdated on the Ansible controller
  • Wrong ansible_winrm_transport or connection variables in inventory

Quick fix: disable cert validation for testing

Most internal labs and staging environments use self-signed certificates. By default, Ansible refuses those connections outright. Add these lines to your inventory or group_vars to bypass cert validation while you diagnose:

[windows:vars]
ansible_user=Administrator
ansible_password=YourPassword
ansible_connection=winrm
ansible_winrm_scheme=https
ansible_port=5986
ansible_winrm_transport=ntlm
ansible_winrm_server_cert_validation=ignore

Then fire a quick connectivity test:

ansible win_host -m win_ping

If that succeeds, the SSL certificate is your culprit โ€” nothing else. Skip ahead to the permanent fix section.

Step 1: Verify WinRM is actually running

RDP or console into the Windows machine and run these in PowerShell:

Get-Service WinRM
winrm enumerate winrm/config/listener

You need a listener showing Transport=HTTPS on port 5986. If the output only shows HTTP on port 5985, the HTTPS listener was never set up.

Stand up WinRM HTTPS with a self-signed cert in one go:

# Run on the Windows host as Administrator
$cert = New-SelfSignedCertificate -DnsName "$(hostname)" -CertStoreLocation Cert:\LocalMachine\My
$thumbprint = $cert.Thumbprint
New-WSManInstance -ResourceURI winrm/config/listener `
  -SelectorSet @{Address="*"; Transport="HTTPS"} `
  -ValueSet @{CertificateThumbprint=$thumbprint}

# Prefer the Ansible-maintained setup script instead:
$url = "https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
$file = "$env:temp\ConfigureRemotingForAnsible.ps1"
(New-Object -TypeName System.Net.WebClient).DownloadFile($url, $file)
powershell.exe -ExecutionPolicy ByPass -File $file

The Ansible script handles cert creation, listener setup, and firewall rules in one shot โ€” worth using if you're setting up more than one host.

Step 2: Open port 5986 in Windows Firewall

New-NetFirewallRule -DisplayName "Ansible WinRM HTTPS" `
  -Direction Inbound `
  -Protocol TCP `
  -LocalPort 5986 `
  -Action Allow

Running on AWS, Azure, or GCP? Don't forget the security group or NSG โ€” Windows Firewall rules mean nothing if the cloud layer is blocking traffic at the VPC level.

From the Ansible controller, test whether port 5986 is actually reachable:

nc -zv 192.168.1.10 5986
# or
curl -k https://192.168.1.10:5986/wsman

Even an HTTP 401 or 500 back from curl means the port is open and WinRM is alive. A connection refused or timeout means the firewall is still blocking it.

Step 3: Install pywinrm on the Ansible controller

No pywinrm, no WinRM connections โ€” Ansible needs this package to speak the protocol at all:

pip install pywinrm
pip install pywinrm[kerberos]   # domain environments using Kerberos
pip install pywinrm[credssp]    # if using CredSSP auth

Confirm it's installed and check the version (you want 0.4.x or later):

pip show pywinrm

Permanent fix: trust the Windows certificate

Shipping server_cert_validation=ignore to production is a bad idea. It opens the door to man-in-the-middle attacks on your automation. The real fix is exporting the Windows host certificate and adding it to the trusted store on your Ansible controller.

On the Windows host, export the cert:

$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Subject -match "$(hostname)"}
Export-Certificate -Cert $cert -FilePath C:\winrm_cert.cer -Type CERT

Copy winrm_cert.cer to your Ansible controller (scp, SMB share, whatever you have), then convert and install it:

# Convert .cer (DER format) to PEM
openssl x509 -inform DER -in winrm_cert.cer -out winrm_cert.pem

# Ubuntu/Debian
cp winrm_cert.pem /usr/local/share/ca-certificates/winrm_cert.crt
update-ca-certificates

# RHEL/CentOS
cp winrm_cert.pem /etc/pki/ca-trust/source/anchors/
update-ca-trust extract

Then update your inventory to validate properly:

[windows:vars]
ansible_winrm_server_cert_validation=validate
ansible_winrm_ca_trust_path=/path/to/winrm_cert.pem

Reference: full working inventory

Here's a complete inventory that works with NTLM over HTTPS โ€” copy and adapt it:

[windows]
win_host ansible_host=192.168.1.10

[windows:vars]
ansible_user=Administrator
ansible_password=YourSecurePassword
ansible_connection=winrm
ansible_winrm_scheme=https
ansible_port=5986
ansible_winrm_transport=ntlm
ansible_winrm_server_cert_validation=ignore

Domain-joined hosts with Active Directory? Switch to Kerberos โ€” it's more secure and handles token delegation properly:

ansible_winrm_transport=kerberos
ansible_user=user@DOMAIN.COM

Verify the fix

Start with win_ping โ€” the lowest-overhead connectivity test Ansible has for Windows:

ansible win_host -m win_ping

Expected output:

win_host | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Once ping passes, run a real command to confirm the full stack works end-to-end:

ansible win_host -m win_shell -a "Get-ComputerInfo | Select-Object WindowsProductName"

Still seeing the SSL error after all of this? Crank verbosity to four and grep the handshake trace:

ansible win_host -m win_ping -vvvv 2>&1 | grep -i ssl

The handshake output will tell you exactly where it's failing โ€” certificate CN mismatch, expired cert, untrusted CA, and so on.

Related Error Notes