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.
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 says | Meaning | Fix | |
|---|---|---|---|
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 address | On 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 error | Turn 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 above | Turn 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 on | Enable 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 dials | Address 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 uses | Fix 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 client | Point 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 all | A firewall silently drops the port (Windows Firewall or a network device), the address is wrong, or the listener is bound to another interface | A 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 s | The 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 WinRM | The 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 reply | A proxy, captive portal or web server answered on that port | Same as above; also check HTTP_PROXY/NO_PROXY on the CertAutoPilot host | |
proxyconnect … / HTTP 407 | The request went through an HTTP proxy configured on the CertAutoPilot host | Add 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 auth | Meaning | Fix | |
|---|---|---|---|---|
Basic, Negotiate | NTLM or Basic | The credential itself was refused, or the account is not authorized for WinRM — see the Security log to tell which | Password; 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 HTTP | NTLM | AllowUnencrypted=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 only | Basic | Basic="false" on the service, or the same AllowUnencrypted withholding | winrm set winrm/config/service/auth '@{Basic="true"}'; on plain HTTP also AllowUnencrypted | ✔ |
Basic only (no Negotiate) | NTLM | Negotiate="false" on the service, or incoming NTLM blocked by Network security: Restrict NTLM: Incoming NTLM traffic | Re-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 only | NTLM | Same as above on a domain-hardened host | Kerberos with the realm and the host's AD FQDN | |
| (none) | any | The handshake completed and WinRM refused the authenticated request | Same checks as the first row | |
| HTTP 403 | any | Authenticated, but the operation is not allowed | The 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) | Kerberos | The 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) | any | The credential authenticated, but the account may not open the WinRS shell — it is in Remote Management Users but not in Administrators | net 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) | any | MaxEnvelopeSizekb 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 file | winrm 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) | any | CertAutoPilot 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 rare | Raise 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 log | Meaning | Fix |
|---|---|---|
4625, Status 0xC000006D, Sub Status 0xC000006A | Wrong password ✔ | Credential |
4625, Sub Status 0xC0000064 | No such user (or wrong domain qualifier) | Username / Domain field on the target |
4625, Status 0xC000015B | The 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 0xC0000072 | Account disabled ✔ | net user <user> /active:yes |
4625, Sub Status 0xC0000234 | Account locked out | Unlock; stop the retrying client first |
4625, Sub Status 0xC0000071 | Password expired | Reset, or set password never expires on the service account |
| No 4625 at all for the attempt ✔ | Authentication succeeded; WinRM refused the account's authorization | Make 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 says | Meaning | Fix |
|---|---|---|
'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 name | Since 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 mode | The 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 fails | Exempt 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 refused | A 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 everything | winrm set winrm/config '@{MaxEnvelopeSizekb="500"}' ✔ |
the account is not a local Administrator / HTTP 500 … Access is denied | Not 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 token | UAC on + local admin + LocalAccountTokenFilterPolicy unset | Set 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 there | File drops still work (they stage beside the destination); fix the ACL to run inline scripts |
the WebAdministration PowerShell module is not installed / IIS_MODULE_MISSING | The IIS Management Scripts and Tools feature is absent | Install-WindowsFeature Web-Scripting-Tools |
IIS (the W3SVC service) is not installed | The target is not an IIS host | Install-WindowsFeature Web-Server, Web-Scripting-Tools |
warning the IIS service (W3SVC) is Stopped | IIS is installed but not running | Bindings 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 deleted | Delete 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 in | Grant the account write on the directory; check the security product's log |
warning Windows PowerShell N is older than the tested 5.1 | Host older than Server 2016 without WMF 5.1 | PowerShell 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 transfer | PowerShell 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 into | Make the account a local Administrator |
warning less than 1 GB free on C: | The system drive is nearly full | The 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 says | Meaning | Fix |
|---|---|---|
KDC_ERR_PREAUTH_FAILED — the KDC rejected the password | Wrong 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 R | The username or the realm is wrong | Bare sAMAccountName; Domain = the account's own AD domain in UPPER CASE |
KDC_ERR_WRONG_REALM | The 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_REVOKED | Account disabled, locked out or expired in AD | Fix in AD Users and Computers |
KDC_ERR_KEY_EXPIRED | Password expired | Reset, or password never expires on the service account |
KRB_AP_ERR_SKEW | Clock skew > 5 min between the CertAutoPilot host and the DC | NTP on the CertAutoPilot host |
KDC_ERR_S_PRINCIPAL_UNKNOWN | No HTTP/<host> SPN — the target is addressed by an alias, not the computer's AD FQDN | Use the FQDN as registered (setspn -L <computer>) |
KDC_ERR_ETYPE_NOSUPP | The KDC offers no encryption type this client supports — the account is restricted to DES/RC4 | Allow AES on the account (This account supports Kerberos AES 128/256 bit encryption) |
any other KDC_ERR_* / KRB_* code | The KDC refused the login for a reason not listed here | The code names it — Microsoft's Kerberos error list decodes it |
Networking_Error … i/o timeout on port 88 | The KDC address is wrong or unreachable, or (without a pinned KDC) DNS cannot serve the AD SRV records | Set 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 DNS | Point 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 name | Configuration refused before any network call | Address 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: | Meaning | Fix |
|---|---|---|
… shell quota … / MaxShellsPerUser / MaxConcurrentUsers | Too 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 … / MaxEnvelopeSizekb | A hardened host lowered the envelope size below what a transfer chunk needs | winrm set winrm/config '@{MaxEnvelopeSizekb="500"}' (the default) |
… operation timed out / MaxTimeoutms | The remote command exceeded the host's WinRM operation timeout | Raise the target's command timeout, or winrm set winrm/config '@{MaxTimeoutms="600000"}' |
… request is not supported | The 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
- IIS module · Windows (WinRM) module · Microsoft Exchange module
- Error codes —
IIS_CONNECT/IIS_AUTH,WINRM_CONNECT/WINRM_AUTH,EXCHANGE_CONNECT/EXCHANGE_AUTH