Skip to main content

WinRM troubleshooting (IIS, Windows, Exchange)

The IIS, Windows (WinRM) and Exchange modules share one transport, so they fail the same way. Since 1.5.53 a failure carries the facts the host gave — not a generic "connection failed" or the old 401 - invalid content type — and this page is the key to reading it. Every row marked ✔ was reproduced on a live Windows Server; the message quoted is what CertAutoPilot shows for it.

Read the message, it names the layer

Since 1.5.54 the message has a fixed shape: one headline saying what happened, then a list of what to check (commands in code), and the raw transport or host text last (Error:, KDC said:, Server said:). In the UI the list can be collapsed and the whole text copied. The headline says where it stopped: nothing is listening, answers plain HTTP, dropped the connection, cannot resolve, not trusted = the connection never reached WinRM (fix the target's host / port / TLS). winrm refused the request: HTTP 401 (server offers …) = WinRM answered and said no (fix auth). HTTP 404 … a web server — not WinRM = the port belongs to a web site. server said: … = WinRM's own fault text (quota, timeout, shell limits).

Before WinRM answers — host, port, TLS, DNS​

The message saysMeaningFix
nothing is listening on host:port (Error: connect: connection refused)No WinRM listener on that port, the WinRM service is stopped, or the listener is bound to another addressOn the host: winrm enumerate winrm/config/listener. The HTTPS listener (5986) is not created by default — winrm quickconfig -transport:https needs a server certificate first; Enable-PSRemoting creates only the HTTP one (5985). Get-Service WinRM must be Running. Firewall: the port must be allowed for the CertAutoPilot host.✔
host:port answers plain HTTP but this target has TLS enabled (Error: http: server gave HTTP response to HTTPS client)The target has TLS on but points at the plain-HTTP listener (5985) — a new or edited target is refused with this combination when saved; only a target saved before 1.5.53 and left untouched still reaches this errorTurn TLS off for 5985, or point the target at 5986✔
host:port dropped the connection after a plain-HTTP request (Error: read: connection reset by peer)The target has TLS off but points at the HTTPS listener (5986) — same save-time refusal as the row aboveTurn TLS on for 5986, or use 5985✔
the TLS certificate on host:port is not trusted by the CertAutoPilot host (Error: x509: certificate signed by unknown authority)The WinRM listener uses a self-signed or private-CA certificate and TLS verification is onEnable skip TLS verification on the target (lab), or install the issuing CA into the CertAutoPilot host's trust store (production)✔
the TLS certificate on host:port was issued for a different name than the one this target dials (Error: x509: certificate is valid for A, not B)The listener certificate was issued for a different name than the one the target dialsAddress the target by the certificate's name, re-issue the listener certificate, or skip verification
the CertAutoPilot host cannot resolve "host" (Error: lookup host: no such host)The name does not exist in the DNS the CertAutoPilot host usesFix the typo, use the FQDN or IP, or fix the resolver (getent hosts <name> on the CertAutoPilot host)✔
the DNS server used by the CertAutoPilot host answered the lookup … with SERVFAIL/REFUSED (Error: lookup host on dns:53: server misbehaving)The resolver answered SERVFAIL/REFUSED — usually an internal AD zone asked of a public resolver, or a DNS server that refuses this clientPoint the CertAutoPilot host's resolver at the DNS that serves the zone; NTLM/Basic targets can use the IP meanwhile (Kerberos needs the FQDN)
could not connect to host:port within N s — no answer from host:port at allA firewall silently drops the port (Windows Firewall or a network device), the address is wrong, or the listener is bound to another interfaceA closed port answers refused, so a timeout is almost never the host itself. Check the Windows Firewall rule for the port and the path in between; on the host, the listener's Address/IPv4Filter
accepted the connection but did not answer the WinRM request within N sThe port is reachable, but the host was too slow to answer (overloaded box, a PowerShell module loading for the first time, an AV scan)Raise the target's connect/command timeout, or look at the host's load. Not a firewall problem
HTTP 404 — the port answered with a web page … a web server — not WinRMThe target's port belongs to IIS or another web site (443/80/8080)WinRM listens on 5985 (HTTP) / 5986 (HTTPS); fix the port✔
answered HTTP 200 with content type "text/html" instead of a WinRM SOAP replyA proxy, captive portal or web server answered on that portSame as above; also check HTTP_PROXY/NO_PROXY on the CertAutoPilot host
proxyconnect … / HTTP 407The request went through an HTTP proxy configured on the CertAutoPilot hostAdd the Windows host to NO_PROXY

WinRM answered 401 — read the offered schemes​

A default listener offers Basic, Negotiate on plain HTTP. What is missing identifies the cause; the message spells it out, this table is the summary.

server offers …Target authMeaningFix
Basic, NegotiateNTLM or BasicThe credential itself was refused, or the account is not authorized for WinRM — see the Security log to tell whichPassword; local Administrator membership (Remote Management Users is not enough — see the Security log); with UAC on, LocalAccountTokenFilterPolicy=1 for a local admin✔
Negotiate only, plain HTTPNTLMAllowUnencrypted=false — Windows withholds the Basic challenge when it will not accept unencrypted traffic, and refuses NTLM too (CertAutoPilot does not encrypt the WSMan body)winrm set winrm/config/service '@{AllowUnencrypted="true"}' on 5985, or move the target to HTTPS 5986 (needs nothing)✔
Negotiate onlyBasicBasic="false" on the service, or the same AllowUnencrypted withholdingwinrm set winrm/config/service/auth '@{Basic="true"}'; on plain HTTP also AllowUnencrypted✔
Basic only (no Negotiate)NTLMNegotiate="false" on the service, or incoming NTLM blocked by Network security: Restrict NTLM: Incoming NTLM trafficRe-enable Negotiate; on a policy-hardened host use a domain account with auth_type=kerberos (Kerberos stays allowed by the baselines that block NTLM)✔
Kerberos onlyNTLMSame as above on a domain-hardened hostKerberos with the realm and the host's AD FQDN
(none)anyThe handshake completed and WinRM refused the authenticated requestSame checks as the first row
HTTP 403anyAuthenticated, but the operation is not allowedThe account's WinRM authorization (RootSDDL, Set-PSSessionConfiguration -ShowSecurityDescriptorUI), or on plain HTTP an unencrypted request
any, Kerberos target (message: the domain controller issued a ticket but the host refused it)KerberosThe KDC accepted the login — a KDC rejection never reaches HTTP, it fails with a KDC_ERR_* code (see Kerberos) — and the host refused the ticket: the account is not an administrator there, or the ticket was for a different service principal (the target is addressed by an alias, not the computer FQDN registered in AD)Administrators membership on the host; address it by the FQDN setspn -L <computer> lists. Advice about Basic or LocalAccountTokenFilterPolicy does not apply to Kerberos
HTTP 500 … Access is denied (message: WinRM refused to open a shell)anyThe credential authenticated, but the account may not open the WinRS shell — it is in Remote Management Users but not in Administratorsnet localgroup Administrators <user> /add✔
The host refused the request because of its SIZE (IIS_HOST_PREREQ / WINRM_QUOTA / EXCHANGE_IMPORT, message …because of its SIZE, not the credential)anyMaxEnvelopeSizekb is below what the transfer needs. Windows answers the over-size request with HTTP 413 (measured live at 32 KB). A 401 that arrives after the shell was opened is reported as an authentication failure — the credential stopped working mid-run (account locked or disabled, a domain controller that stopped answering) — with MaxEnvelopeSizekb named as the thing to check if it only ever happens on the file transfer (older hosts answered an over-size request that way). Before this was detected, the same refusal surfaced as a transfer content check failed — the request never delivered the filewinrm set winrm/config '@{MaxEnvelopeSizekb="500"}' (the default). The health check warns about a low limit before any deploy, with the largest transfer that still fits✔
Commands fail only on one host after the upgrade, with shell errors (The request for the Windows Remote Shell with ShellId … failed, shell not found)anyCertAutoPilot now runs a target's commands in ONE WinRM shell; a host that drops idle shells aggressively (a very low IdleTimeout, a third-party WinRM filter) may close it between commands. The module already replaces a shell the host refused before a command started, so this should be rareRaise winrm/config/Winrs IdleTimeout, or set CERTAUTOPILOT_WINRM_SHELL_REUSE=false in the backend's environment and restart it to go back to a shell per command (Helm: add it under extraEnv so it survives upgrades)✔

Username form is set by the target, not by you. For NTLM enter the bare account name; CertAutoPilot sends DOMAIN\user when the target has a Domain and .\user (explicitly local) when it has none. For Basic the username must stay bare — Basic cannot parse a .\ prefix. For Kerberos the name must be bare and the target addressed by its AD FQDN; the construction error says so if not.

The Security log tells password from authorization apart​

Windows answers a wrong password and a valid account that is merely not an administrator with the same bare 401. The host's Security log separates them (Event Viewer → Windows Logs → Security, filter Event ID 4625). The rows marked ✔ were reproduced on a live Windows Server; the others are the documented Windows status codes for the same event:

In the Security logMeaningFix
4625, Status 0xC000006D, Sub Status 0xC000006AWrong password ✔Credential
4625, Sub Status 0xC0000064No such user (or wrong domain qualifier)Username / Domain field on the target
4625, Status 0xC000015BThe account may not log on over the network ✔ — a "Deny access to this computer from the network" right covers it (Microsoft's security baseline adds Local account and member of Administrators group there)Remove the account/group from that right (secpol.msc → User Rights Assignment), or use a domain account
4625, Sub Status 0xC0000072Account disabled ✔net user <user> /active:yes
4625, Sub Status 0xC0000234Account locked outUnlock; stop the retrying client first
4625, Sub Status 0xC0000071Password expiredReset, or set password never expires on the service account
No 4625 at all for the attempt ✔Authentication succeeded; WinRM refused the account's authorizationMake it a local Administrator (net localgroup Administrators <user> /add). Remote Management Users is not enough: it grants the PowerShell remoting endpoint, while CertAutoPilot runs its commands through the WinRS shell, which the default security descriptor grants to Administrators only — such an account gets HTTP 500 … Access is denied (reproduced live), and the certificate import needs an administrator anyway

CertAutoPilot does not retry a 401/403: the refusal is permanent until the host or the target changes, and every retry would add a failed-logon record (and, with a lockout policy, lock the account out mid-deploy).

LocalAccountTokenFilterPolicy — only with UAC on​

A local administrator over a network logon gets a filtered (non-admin) token when UAC is enabled, so WinRM treats it as a standard user. LocalAccountTokenFilterPolicy=1 disables that filtering for network logons. It matters only when EnableLUA=1 (UAC on, the default) — on a host with UAC disabled the value has no effect, which is why it does not always reproduce in labs. Not needed for the built-in Administrator or for domain accounts.

$p = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
$p.EnableLUA # 1 = UAC on
$p.LocalAccountTokenFilterPolicy # must be 1 for a local admin
New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' `
-Name LocalAccountTokenFilterPolicy -Value 1 -PropertyType DWORD -Force

Large transfers are not an auth problem​

Earlier guidance suggested switching to Basic when only the PFX upload failed. Re-tested against the current transport (the payload streams on the remote process's stdin): 64 KiB, 1 MiB and 4 MiB uploads all succeed over NTLM. If a small command works and only an upload fails, look at the host — free space on C:\Windows\Temp, an AV/EDR hook on file writes, a WinRM quota — the message quotes WinRM's fault text when there is one.

The host cannot run the deploy — prerequisites​

The health check of an IIS or Windows target now runs a preflight script instead of Write-Output OK, and reports every prerequisite the host lacks, with its fix, as IIS_HOST_PREREQ / WINRM_HOST_PREREQ (a missing WebAdministration module alone stays IIS_MODULE_MISSING), and lists the conditions that do not stop a deploy as warnings. At deploy time, Constrained Language mode and a refused-for-size transfer are recognised too; the other rows are health-check findings (during a deploy a non-administrator account shows up as IIS_AUTH / WINRM_AUTH). Each row was reproduced on a live Windows Server unless marked otherwise.

Health check / deploy saysMeaningFix
'powershell' is not recognized as an internal or external command (in a picker, health check or deploy, before 1.5.53)The host's Path environment variable lost C:\Windows\System32\WindowsPowerShell\v1.0 (an installer or policy overwrote it), and older releases started PowerShell by nameSince 1.5.53 CertAutoPilot starts PowerShell by its full path, so this no longer happens. On an older release, add the directory back to the machine Path (System Properties → Environment Variables) and run Restart-Service WinRM — remote sessions keep the environment the WinRM service started with.
PowerShell language mode is ConstrainedLanguage — at deploy time Cannot invoke method. Method invocation is supported only on core types in this language modeThe host locks PowerShell to Constrained Language: Windows Defender Application Control or AppLocker script enforcement, or the __PSLockdownPolicy system environment variable. Trivial commands still run (the old health check passed here), every deploy script failsExempt the deploy account or CertAutoPilot's scripts in the policy, or remove the lockdown variable (Remove-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -Name __PSLockdownPolicy). Note: the WinRM host process caches the variable — a reboot may be needed after changing it. ✔
warning WinRM MaxEnvelopeSizekb is N on the host — a transfer larger than about M KB is refusedA hardened host lowered the WinRM envelope limit. A certificate upload is small and still fits (measured live: a deploy succeeded at 32 KB); a large staged script (IIS multi-binding) or a large generic file drop does not, and fails with the size error above. The transfer sends ~150 KB pieces of payload, ~201 KB per request once encoded, so the default 500 fits everythingwinrm set winrm/config '@{MaxEnvelopeSizekb="500"}' ✔
the account is not a local Administrator / HTTP 500 … Access is deniedNot in Administrators (Remote Management Users is not enough)net localgroup Administrators USERNAME /add. IIS: a failure — the certificate import and the binding update need it. Windows (generic): a warning — the module can run as a non-administrator the host granted WinRS access to, within that account's own file and service rights ✔
the account is a local Administrator but arrives with a UAC-filtered tokenUAC on + local admin + LocalAccountTokenFilterPolicy unsetSet the value to 1 (see above). Failure for IIS, warning for the generic module
the account cannot write to its own %TEMP% (generic module, warning)Inline ActionSet scripts stage thereFile drops still work (they stage beside the destination); fix the ACL to run inline scripts
the WebAdministration PowerShell module is not installed / IIS_MODULE_MISSINGThe IIS Management Scripts and Tools feature is absentInstall-WindowsFeature Web-Scripting-Tools
IIS (the W3SVC service) is not installedThe target is not an IIS hostInstall-WindowsFeature Web-Server, Web-Scripting-Tools
warning the IIS service (W3SVC) is StoppedIIS is installed but not runningBindings are still updated in the configuration — the deploy succeeds — but the sites do not serve until Start-Service W3SVC ✔
… already exists and could not be removed / … could not be restricted to the connecting account (icacls exit N); the file was removed — IIS_HOST_PREREQ at deploy time (IIS)Staging files are created as new files readable only by the connecting account, SYSTEM and Administrators. A leftover .captmp from an interrupted run that the account cannot delete is refused rather than written into (Windows would keep that file's own permissions); on a host that refuses the restricted create, icacls restricts the file instead, and when that fails too the file is deletedDelete the leftover file named in the message (as an administrator), or fix what blocks icacls on C:\Windows\Temp (an AV/EDR hook, a deny ACL)
the account cannot write to C:\Windows\Temp (IIS)ACL or an AV/EDR hook blocks the directory the certificate and scripts stage inGrant the account write on the directory; check the security product's log
warning Windows PowerShell N is older than the tested 5.1Host older than Server 2016 without WMF 5.1PowerShell 3 and 4 do not fail the check; if a script fails on this host, install WMF 5.1 first
Windows PowerShell N cannot run the file transferPowerShell 2 (Server 2008 R2 without WMF) — the transfer needs PowerShell 3+Install WMF 5.1
the machine certificate store (Cert:\LocalMachine\My) is not accessible to the account (IIS)The account cannot open the local machine store the PFX is imported intoMake the account a local Administrator
warning less than 1 GB free on C:The system drive is nearly fullThe staged PFX and script are small, but a full disk fails the transfer — free space ✔

Things that do not matter, verified live: the machine execution policy (Restricted included — the scripts run through -EncodedCommand / -ExecutionPolicy Bypass -File), and whether IIS is currently running.

Kerberos — the KDC names the cause​

With auth_type=kerberos a login failure quotes the KDC's own error code and the message translates it. Each row was produced against a live Active Directory:

The message saysMeaningFix
KDC_ERR_PREAUTH_FAILED — the KDC rejected the passwordWrong password (or the account requires a smart card / is limited to certain workstations)Credential
KDC_ERR_C_PRINCIPAL_UNKNOWN — no account named X in realm RThe username or the realm is wrongBare sAMAccountName; Domain = the account's own AD domain in UPPER CASE
KDC_ERR_WRONG_REALMThe Domain field is not the account's realm (an alias, a parent/child domain)Set it to the account's AD domain
KDC_ERR_CLIENT_REVOKEDAccount disabled, locked out or expired in ADFix in AD Users and Computers
KDC_ERR_KEY_EXPIREDPassword expiredReset, or password never expires on the service account
KRB_AP_ERR_SKEWClock skew > 5 min between the CertAutoPilot host and the DCNTP on the CertAutoPilot host
KDC_ERR_S_PRINCIPAL_UNKNOWNNo HTTP/<host> SPN — the target is addressed by an alias, not the computer's AD FQDNUse the FQDN as registered (setspn -L <computer>)
KDC_ERR_ETYPE_NOSUPPThe KDC offers no encryption type this client supports — the account is restricted to DES/RC4Allow AES on the account (This account supports Kerberos AES 128/256 bit encryption)
any other KDC_ERR_* / KRB_* codeThe KDC refused the login for a reason not listed hereThe code names it — Microsoft's Kerberos error list decodes it
Networking_Error … i/o timeout on port 88The KDC address is wrong or unreachable, or (without a pinned KDC) DNS cannot serve the AD SRV recordsSet the target's KDC Address to a reachable DC, or fix DNS/firewall
could not discover a KDC via DNS (_kerberos._tcp.REALM)The CertAutoPilot host's resolver is not the AD DNSPoint it at AD DNS, or set KDC Address
auth_type=kerberos cannot be used with an IP address / requires domain (realm) / requires a bare account nameConfiguration refused before any network callAddress the target by FQDN, set the realm, remove the DOMAIN\ / @ qualifier

Exchange targets are Kerberos-only, so every row applies to them as well.

Retry behaviour: a verdict from the KDC (every row that quotes a KDC_ERR_* / KRB_* code) is permanent — IIS_AUTH / WINRM_AUTH / EXCHANGE_AUTH, never retried, so a wrong password cannot lock the account out through a fan-out. A KDC that cannot be reached (the last two network rows) stays IIS_CONNECT / WINRM_CONNECT / EXCHANGE_CONNECT and is retried like any dial failure.

WinRM answered with a fault — server said: …​

WinRM reports its own limits as SOAP faults; the message quotes the fault text instead of the XML. The common ones:

server said:MeaningFix
… shell quota … / MaxShellsPerUser / MaxConcurrentUsersToo many open shells for the account or host (a previous run left shells behind, or several deploys run at once)winrm set winrm/config/winrs '@{MaxShellsPerUser="50"}'; shells expire after IdleTimeout
… envelope size … / MaxEnvelopeSizekbA hardened host lowered the envelope size below what a transfer chunk needswinrm set winrm/config '@{MaxEnvelopeSizekb="500"}' (the default)
… operation timed out / MaxTimeoutmsThe remote command exceeded the host's WinRM operation timeoutRaise the target's command timeout, or winrm set winrm/config '@{MaxTimeoutms="600000"}'
… request is not supportedThe URL prefix is not /wsman (a custom listener)Use the default listener

Testing a host from the CertAutoPilot machine​

Reproduce what CertAutoPilot does without CertAutoPilot — the offered schemes are in the reply:

# plain HTTP listener — what does it offer?
curl -s -i -X POST http://<host>:5985/wsman \
-H 'Content-Type: application/soap+xml;charset=UTF-8' --data x | grep -i 'HTTP/\|WWW-Authenticate'

# NTLM with the credential (401 with a body = refused; 500 with a SOAP fault = authenticated, the dummy body was rejected — that is success for auth)
curl -sk -i -u 'user:password' --ntlm -X POST https://<host>:5986/wsman \
-H 'Content-Type: application/soap+xml;charset=UTF-8' --data x | head -20

See also​