Windows Server Patching with Ansible

Eran Goldman-Malka · November 20, 2025

Windows patching through Ansible gets dismissed as complicated, then adopted by teams who realise the alternative is RDP sessions, manual Windows Update clicks, and no audit trail. WinRM is not SSH, and the setup has sharper edges—but once it is correctly configured and locked down, the automation model is identical to Linux: one playbook, rolling batches, post-reboot validation.

WinRM vs SSH

Ansible supports both WinRM (the traditional path) and OpenSSH (available since Windows Server 2019). For most enterprise environments WinRM is already present; OpenSSH is the cleaner long-term choice if you control the server builds.

  WinRM OpenSSH
Available Windows Server 2008+ Windows Server 2019+
Default port 5985 (HTTP) / 5986 (HTTPS) 22
Auth options Basic, Kerberos, NTLM, Certificate Key-based, password
Encryption Requires HTTPS or message-level encryption Always encrypted
Recommendation Use HTTPS (5986) only Preferred on modern builds

WinRM Setup on the Target (Run Once Per Host)

Run this on each Windows Server target in an elevated PowerShell session:

# Enable and configure WinRM
winrm quickconfig -q

# Create a self-signed certificate for HTTPS (replace with a proper cert in production)
$cert = New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:\LocalMachine\My
New-Item -Path WSMan:\localhost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $cert.Thumbprint -Force

# Open the firewall for WinRM HTTPS
New-NetFirewallRule -DisplayName "WinRM HTTPS" -Direction Inbound -LocalPort 5986 -Protocol TCP -Action Allow

# Set the authentication method
Set-Item WSMan:\localhost\Service\Auth\Basic -Value $true   # only if not using Kerberos/NTLM

For domain-joined servers, use Kerberos authentication instead of Basic—it doesn’t transmit credentials over the wire.


Security Warning: WinRM HTTP Is Not Safe

The most common WinRM misconfiguration is leaving HTTP (port 5985) open on the network with Basic authentication enabled. This transmits credentials in Base64 (effectively cleartext) and has been exploited in lateral movement attacks.

Minimum safe configuration:

  • HTTPS only (port 5986, valid certificate)
  • Basic auth disabled unless you have no Kerberos option
  • WinRM firewall rule restricted to the Ansible control node IP
  • AllowUnencrypted set to false:
Set-Item WSMan:\localhost\Service\AllowUnencrypted -Value $false

Required Collection

Install the ansible.windows collection before running any Windows playbooks:

ansible-galaxy collection install ansible.windows

Add it to requirements.yml:

collections:
  - name: ansible.windows
    version: ">=2.0.0"

Inventory for Windows Hosts

# inventory/production/hosts.yml
all:
  children:
    windows_servers:
      hosts:
        win01.example.com:
        win02.example.com:
      vars:
        ansible_connection: winrm
        ansible_winrm_transport: ntlm          # or kerberos for domain-joined hosts
        ansible_winrm_scheme: https
        ansible_port: 5986
        ansible_winrm_server_cert_validation: validate   # set to ignore only in lab
        ansible_user: "DOMAIN\\ansible_svc"
        ansible_password: ""

Core Modules: win_updates and win_reboot

Apply all critical and security updates:

---
- name: Patch Windows servers
  hosts: windows_servers
  serial: 5

  tasks:
    - name: Install security and critical updates
      ansible.windows.win_updates:
        category_names:
          - SecurityUpdates
          - CriticalUpdates
        state: installed
        reboot: false           # handle reboot explicitly below
      register: update_result

    - name: Show update summary
      ansible.builtin.debug:
        msg: >
          :  updates installed,
          reboot required: 

    - name: Reboot if required
      ansible.windows.win_reboot:
        reboot_timeout: 600
        post_reboot_delay: 30
      when: update_result.reboot_required | bool

Update Categories

The category_names parameter maps to Windows Update categories:

Category Description
SecurityUpdates Microsoft Security Response Center patches
CriticalUpdates Non-security critical fixes
UpdateRollups Cumulative rollup packages
Updates General product updates
DefinitionUpdates Defender / AV signature updates
Drivers Hardware driver updates (usually excluded from server patching)

For patch management, start with SecurityUpdates and CriticalUpdates. Add UpdateRollups for monthly cumulative patching cycles.


Filtering to Specific KBs

- name: Install a specific KB only
  ansible.windows.win_updates:
    whitelist:
      - KB5034441
    state: installed
    reboot: false
- name: Exclude a known-bad KB
  ansible.windows.win_updates:
    category_names:
      - SecurityUpdates
    blacklist:
      - KB5034441   # blocked pending vendor compatibility test
    state: installed
    reboot: false

Handling Long-Running Updates and Retries

Some cumulative updates take 20–40 minutes. Set reboot_timeout generously and implement retries for the update task:

- name: Install updates with retry
  ansible.windows.win_updates:
    category_names:
      - SecurityUpdates
      - CriticalUpdates
    state: installed
    reboot: false
  register: update_result
  retries: 3
  delay: 60
  until: update_result is not failed

- name: Reboot with extended timeout
  ansible.windows.win_reboot:
    reboot_timeout: 1800      # 30 minutes for slow cumulative updates
    post_reboot_delay: 60
    test_command: whoami
  when: update_result.reboot_required | bool

Post-Patch Service Validation

- name: Verify critical Windows services are running
  ansible.windows.win_service:
    name: ""
    state: started
  loop:
    - W32Time
    - WinRM
    - EventLog
    - Dnscache
  register: svc_check

- name: Fail if services did not recover
  ansible.builtin.fail:
    msg: " is not running on "
  loop: ""
  when: item.state != "running"

Scheduling vs On-Demand

For scheduled patching, integrate with the CI/CD pipeline patterns from part four—a cron-triggered GitHub Actions or GitLab CI job that targets the windows_servers group on a defined cadence. On-demand patching (emergency CVE response) uses the same playbook with --limit to scope to affected hosts only:

ansible-playbook -i inventory/production/hosts.yml playbooks/patch-windows.yml \
  --limit "win01.example.com,win02.example.com" \
  --tags security

Previous: Linux Server Patching with Ansible

Next in the series: Day-to-Day Automation and Reporting — scheduling with AWX, compliance reporting, SIEM integration, and drift detection.

Twitter, Facebook