Helpdesk: "Since about 7:30 this morning the 802.1X Wi-Fi rejects every laptop, the VPN says the certificate couldn't be validated, and the issuing CA service won't start. Its error says the revocation function was unable to check revocation because the revocation server was offline, 0x80092013. The PKI admin is out; the offline root has been powered off in a closet since spring. A forum post says to set CRLF_REVCHECK_IGNORE_OFFLINE on the CA, set NoRevocationCheck on both NPS servers, and turn off revocation checking in the VPN profile. Can we start with that?"
Not yet. "Revocation server offline" almost never means a server is down. It means a client building a certificate chain couldn't get a current CRL for one of the certificates in it: the CRL expired, the CDP URL can't be reached from that client's network or security context, the URL was never published, or the client is still using a stale cached copy. Microsoft's DirectAccess article for 0x80092013 lists those four causes word for word, starting with "The CRL location (CDP) is unreachable." and ending with "The CRL is reachable but the client is picking from an old cache." Its chain-validation note explains the blast radius: "Revocation checking is performed from the root CA certificate down to the evaluated certificate." One expired root CRL breaks every certificate under it, and Wi-Fi, VPN, and the CA itself fail together. Turning off revocation checking hides the failed link and leaves it failed, and a CRL that's still expired tomorrow breaks the next app nobody exempted. Work through it as a chain: which app, which error code, which certificate, which context, since when → certutil -verify -urlfetch on that certificate, from the failing machine → which CRL failed: issuing, root, or delta, and its NextUpdate → can this client, in this context, reach the CDP URL? → does the web or LDAP location serve the current file? → CAPI2 events, which name the exact action that failed → caches → the CA: publication events, schedule, and CDP settings → the offline root's CRL → what each relying party does with an unknown result. certutil, Get-WinEvent, wevtutil, Invoke-WebRequest, Test-NetConnection, Get-CACrlDistributionPoint, and Get-ItemProperty are the sources for that chain. Set the ignore flags everywhere, and you'll be turning them back off one app at a time, with the CRL still expired.
If certificates aren't arriving at all, start with Certificate Autoenrollment Not Working; its Step 9 is where this post picks up. If the failure is a Wi-Fi profile on Intune-managed devices, Intune Wi-Fi Profile Not Applying covers the profile side. This post covers "Windows clients, servers, or the CA itself fail to validate certificates from an AD CS hierarchy with 'the revocation server was offline' (0x80092013) or 'unable to check revocation' (0x80092012), and you need a read-first path before you turn off revocation checking, set ignore-offline flags, or republish over the top."
TL;DR
-
Read the chain before you touch the CA.
certutil -verify -urlfetchon the failing certificate fetches every AIA and CDP URL in the chain and shows which one failed (Step 2). Microsoft's autoenrollment blog: "Verify that all certification authorities in the chain have valid CRL's published." - Check NextUpdate on every CRL in the chain. Microsoft's CAPI2 guide: "If the current date is later than the date in the nextUpdate field in the CRL, it means that the CRL has expired." The offline root's CRL is the usual suspect, because nothing publishes it on a schedule (Steps 3, 9).
- Test from the context that fails. Microsoft's 0x80092013 article says to "verify that the CRL is downloadable from the system context". Services run as SYSTEM with WinHTTP proxy settings, not yours (Step 4).
-
Publishing isn't delivering. Microsoft:
certutil -CRL"writes new CRL data according to the CA configuration; it doesn't prove that a web server, replication process, or external transfer makes the new files available." (Step 5). -
A fixed CRL can still fail from cache. Microsoft's fix for the old-cache cause is
certutil -setreg chain\ChainCacheResyncFiletime @nowandcertutil -urlcache * delete(Steps 7, 11). - Ignore-offline flags are a stopgap with an owner and an end date, not a fix. The CA flag, the NPS keys, and the curl and Git switches all weaken validation (Step 10, common false starts).
Where in the chain did it break?
| Step | What should be true | Typical symptom when it breaks |
|---|---|---|
| 1. Symptom captured | Which app, error code, certificate, context, network, since when | "Certificates are broken" with no certificate exported |
| 2. Chain read |
certutil -verify -urlfetch run on the failing certificate, in the failing context |
Only the admin's browser was tested |
| 3. CRL time-valid | Every CRL in the chain is before its NextUpdate | Root CRL expired over a weekend |
| 4. CDP reachable | The client can resolve and reach the first usable CDP URL as the identity that validates | Works for the admin, fails as SYSTEM or over VPN |
| 5. Location current | HTTP and LDAP locations serve the CRL the CA last generated | CA has a new CRL; the web server has last month's |
| 6. CAPI2 read | The failing action is named | "Offline" with no detail |
| 7. Caches | The client uses the current CRL, not a cached old one | Fixed on the server, still failing on clients |
| 8. CA side | CRLs publish on schedule to every location; CDP settings match what certificates carry | Events 65/74 nobody reads; CDP URL changed last year |
| 9. Offline root | The root's CRL was renewed and distributed before NextUpdate | The root has been off since its last CRL |
| 10. Relying parties | Each app's revocation policy is known and unchanged | NPS keys flipped during the last outage and never flipped back |
| 11. Change | Only after Steps 1-10 agree: one link, one change | Ignore flags everywhere, same expired CRL |
Step 1: Capture the symptom (read-only)
Write these on the ticket first:
| Fact | Why it matters |
|---|---|
| Which app, and its exact error | 0x80092013 and 0x80092012 are different results (table below); app wording varies, the code doesn't |
| Which certificate | Export it (public part only, .cer) from the app's server, the client's store, or the CA. Step 2 needs the file |
| Which context validates it | A service, NPS, the CA service, and scheduled tasks validate as SYSTEM or a service account; a browser validates as the user (our reading) |
| Which network | LAN, VPN, Wi-Fi onboarding VLAN, internet. A CDP reachable from one isn't proof for another |
| Since when, and what changed | A date matching a CRL's NextUpdate is the strongest lead; also CA renewal, web server or DNS change, proxy change, firewall change |
The codes, each from Microsoft's Winerror.h page:
| Code | Name | Microsoft's text | Usually points at (our reading unless noted) |
|---|---|---|---|
| 0x80092013 | CRYPT_E_REVOCATION_OFFLINE |
"The revocation function was unable to check revocation because the revocation server was offline." | A CRL that couldn't be retrieved or isn't valid (Steps 3-7) |
| 0x80092012 | CRYPT_E_NO_REVOCATION_CHECK |
"The revocation function was unable to check revocation for the certificate." | No usable revocation information at all, often no CDP; common from curl and Git on Windows (Step 10) |
| 0x80092010 | CRYPT_E_REVOKED |
"The certificate is revoked." | Revocation worked: the certificate is revoked. Don't clear caches to make it go away |
| 0x80090358 | SEC_E_REVOCATION_OFFLINE_KDC |
"The revocation status of the domain controller certificate used for smartcard authentication could not be determined." | Smart card or certificate logon: the DC certificate's CRL (Step 10) |
Then branch on what you see:
| What you see | What it usually means (our reading unless noted) | Start at |
|---|---|---|
| Everything certificate-based failed at the same time | One CRL high in the chain expired, usually the root's | Steps 2, 3, 9 |
| CA service won't start, Event 100 or 48 | The CA can't check its own chain: usually the parent CA's CRL (Microsoft's Event 100 page) | Steps 8, 9 |
| Works on the LAN, fails over VPN or the internet | The CDP URL is internal-only, or LDAP-only | Step 4 |
| Works for you, fails for the service | SYSTEM proxy, or a CDP that needs authentication | Step 4 |
| Fails right after a CRL was republished | Stale web copy, or cached old CRL | Steps 5, 7 |
| Base CRL fine, delta fails | Delta not served (IIS double escaping), or not published | Step 5 |
| Pages slow, no error | Revocation fetches timing out (SharePoint's 15-second example) | Steps 4, 10 |
Step 2: Read the chain with certutil -verify -urlfetch (read-only)
This is the one test that answers most of the questions at once. Microsoft's autoenrollment blog: "The client does not have a valid certificate revocation list (CRL) from the issuing CA that it can use to check if a certificate has been revoked." Its test is to run certutil -v -verify -urlfetch against the issuing CA's certificate. Microsoft's CDP article uses the same test for any certificate: "From each representative network zone, validate the test certificate and retrieve its revocation data". Run it on the certificate that fails, on the machine that fails:
:: Read-only (writes one local file). Verify the chain and fetch every AIA and CDP URL in it (file names are examples)
certutil -v -verify -urlfetch C:\Temp\failing.cer > C:\Temp\verify-failing.txt
:: Read-only (writes one local file). The issuing CA's own certificate, from the CA (config string is an example)
certutil -config "CA01.contoso.com\Contoso Issuing CA" -ca.cert C:\Temp\issuing-ca.cer
certutil -verify -urlfetch C:\Temp\issuing-ca.cer
Microsoft's description of -verify: "Verifies a certificate, certificate revocation list (CRL), or certificate chain." Microsoft's caution on running it: "it will use the security and proxy settings of the user that runs it". An admin session that passes doesn't clear the SYSTEM account; Step 4 has the SYSTEM form.
How to read the output (structure from the sample output a user posted on Microsoft Q&A, community; status wording is our reading):
| Part of the output | What it tells you |
|---|---|
One CertContext[0][n] block per certificate |
[0][0] is the certificate you passed; the last one is the root |
Certificate AIA / Certificate CDP under each block |
Each URL that block's certificate points at, with a status. Verified is good; anything else is the lead |
Base CRL CDP |
Where the CRL says its delta CRL lives (empty if the CA doesn't publish deltas) |
CRL nn: with ThisUpdate / NextUpdate
|
The CRL that was used, and when it stops being valid |
| The last lines | A pass shows a completed message; a failure names the error code (our reading) |
# Read-only. Pull the lines that matter out of the saved output (pattern list is ours)
Select-String -Path C:\Temp\verify-failing.txt -Pattern 'CertContext|CDP|CRL \d|ThisUpdate|NextUpdate|Verified|Expired|Failed|0x8009' |
Select-Object LineNumber, Line
| Finding | What it means (our reading unless noted) | Next |
|---|---|---|
| The root's block shows no CDP | Normal. Microsoft's CAPI2 guide: a CDP is necessary "for any certificate except for a root CA certificate" | The next block down |
The issuing CA's block: CDP URL not Verified
|
The root's CRL (it covers the issuing CA's certificate) is expired or unreachable | Steps 3, 9 |
The leaf's block: CDP URL not Verified
|
The issuing CA's CRL is expired or unreachable | Steps 3-5 |
NextUpdate in the past on any CRL |
Expired. Republish it (Step 11) | Step 3 |
First CDP URL fails, a later one is Verified
|
Validation can still pass, but slowly; Microsoft says Windows clients try them in order | Step 4 |
| Passes for you, fails in the app | Different context, proxy, or network | Step 4 |
| A block with no CDP and no OCSP that isn't the root | Nothing to check against: 0x80092012 territory | Step 8: the CA's CDP settings |
Step 3: Which CRL, and is it still time-valid? (read-only)
Every CA in the chain except the root's own certificate needs a current CRL: the root's CRL covers the issuing CA's certificate, and the issuing CA's CRL covers the leaf. Microsoft's CAPI2 guide on what expired looks like: "If the current date is later than the date in the nextUpdate field in the CRL, it means that the CRL has expired." Its fix is one line: "To fix this error, publish a time-valid CRL on the server."
Read the CRLs the clients actually download, not only the CA's copy. Take the HTTP URL from Step 2's output:
# Read-only (writes local files). Download each CRL from its HTTP CDP and read its validity (URLs are examples)
$crls = @{
'issuing' = 'http://pki.contoso.com/pki/ContosoIssuingCA.crl'
'root' = 'http://pki.contoso.com/pki/ContosoRootCA.crl'
}
foreach ($name in $crls.Keys) {
Invoke-WebRequest -Uri $crls[$name] -OutFile "C:\Temp\$name.crl" -UseBasicParsing
"== $name"
certutil -dump "C:\Temp\$name.crl" | Select-String -Pattern 'ThisUpdate|NextUpdate'
}
certutil -dump "Dumps the configuration information or files." certutil -URL opens the same check interactively: it "Verifies certificate or CRL URLs." On the CA, read what the CA itself generated and what it thinks it published:
# Read-only. On the CA: the CRL files the CA writes locally (default CertEnroll path) and their timestamps
Get-ChildItem -Path "$env:SystemRoot\System32\CertSrv\CertEnroll" -Filter *.crl |
Select-Object Name, Length, LastWriteTime
:: Read-only. On the CA: base and delta CRL publish status, then the CRL schedule values (Name* is certutil's prefix match)
certutil -CAInfo crlstatus
certutil -CAInfo deltacrlstatus
certutil -getreg ca\CRLPeriod*
certutil -getreg ca\CRLOverlap*
certutil -getreg ca\CRLDelta*
certutil -CAInfo lists crlstatus as "CRL Publish Status" and deltacrlstatus as "Delta CRL Publish Status". The CRL period is how long each CRL is valid; the overlap is the head start clients get. Microsoft's two-tier PKI lab guide: "The overlap period for CRLs is the amount of time at the end of a published CRLs lifetime that a client can use to obtain a new CRL before the old CRL is considered unusable". And: "The default setting for this value is 10% of the CRL lifetime." Microsoft's CDP article keeps the two ideas apart: "A publishing interval and a CRL validity period are different settings."
LDAP CDPs live in AD. Read when each CDP object last changed (our form; whenChanged moves when a CRL is written there, our reading):
# Read-only. CRL distribution point objects in the configuration partition and when each last changed
$config = (Get-ADRootDSE).configurationNamingContext
Get-ADObject -SearchBase "CN=CDP,CN=Public Key Services,CN=Services,$config" `
-Filter "objectClass -eq 'cRLDistributionPoint'" -Properties whenChanged |
Select-Object DistinguishedName, whenChanged | Sort-Object whenChanged
certutil -dsCRL "Displays DS CRLs." Add -config to point it at one CA (our reading of its options).
| Finding | What it means (our reading unless noted) | Next |
|---|---|---|
| Root CRL past NextUpdate | Nobody renewed the offline root's CRL | Step 9 |
| Issuing CRL past NextUpdate, CA running | The CA isn't publishing, or publishes somewhere clients don't read | Steps 5, 8 |
| CertEnroll copy current, HTTP copy old | Publication worked, delivery didn't | Step 5 |
| HTTP copy current, LDAP object old | LDAP publication failing (Events 65/74) | Step 8 |
| All current, still failing | Reachability, context, or cache | Steps 4, 7 |
| Overlap of zero or a few minutes | Clients get no head start; any delay becomes an outage | Step 11 |
Step 4: Can this client, in this context, reach the CDP? (read-only)
Microsoft's 0x80092013 article lists five reasons a CRL location is unreachable, including "System context proxy is applied but unreachable and requires user authentication." and "CRL Location requires authentication before access is granted." Microsoft's CDP article puts the test plainly: "Test the exact CDP URL from that client network."
Name and port first, from the failing machine:
# Read-only. The CDP host from Step 2: DNS answer, then TCP 80 (host name is an example)
$cdpHost = 'pki.contoso.com'
Resolve-DnsName -Name $cdpHost
Test-NetConnection -ComputerName $cdpHost -Port 80 -WarningAction SilentlyContinue |
Select-Object ComputerName, RemoteAddress, TcpTestSucceeded
Then the context. Services don't use your browser's proxy. Read the machine's WinHTTP proxy and the two hives Microsoft's article checks for the system context:
:: Read-only. WinHTTP proxy (what many services use), then the proxy values Microsoft's 0x80092013 article reads
netsh winhttp show proxy
reg query "HKU\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyEnable
reg query "HKU\S-1-5-18\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyServer
reg query "HKU\S-1-5-18\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v AutoConfigURL
Then repeat Step 2 as SYSTEM. Sysinternals PsExec's -s switch will "Run the remote process in the System account." Run locally, it does the same on this machine (our reading):
:: Read-only (writes one local file). Step 2's test as SYSTEM on the failing machine
psexec -s cmd /c "certutil -verify -urlfetch C:\Temp\failing.cer > C:\Temp\verify-system.txt"
Then the URL list. Microsoft's PKI design page: "Remember that Windows clients always retrieve the list of URLs in sequential order until a valid CRL is retrieved." An LDAP URL first in the list costs a non-domain or off-network client a timeout before it reaches HTTP. The same page: "Use HTTP CDP locations to provide accessible CRL locations for clients running non-Windows operating systems." For Entra joined devices, Microsoft's Windows Hello for Business guide says the same: "Publish your certificate revocation list to a location that is available to Microsoft Entra joined devices, such as a web-based URL". Microsoft's CAPI2 guide rules one scheme out: "CryptoAPI does not support the FTP protocol for network retrieval."
| Finding | What it means (our reading unless noted) | Next |
|---|---|---|
| Name doesn't resolve off-LAN | Internal-only DNS name in the CDP | Step 11: an HTTP name every zone can resolve, for new certificates (Step 8) |
| TCP 80 blocked from the VPN pool | Firewall rule missing for the CDP host | Network change for that one host and port |
| Passes as you, fails as SYSTEM | WinHTTP or system-context proxy | Step 11: proxy bypass for the CDP host |
| Only LDAP URLs in the CDP | Non-domain, Entra joined, and off-network clients can't use them | Step 8: add HTTP for new certificates |
| The proxy asks for credentials | SYSTEM can't answer (Microsoft's cause) | Proxy exception for the CDP host |
Step 5: Does the web or LDAP location serve the current file? (read-only)
The CA writing a CRL and a client getting it are separate jobs. Microsoft: "The CA's local generation result doesn't validate the delivery chain." Its monitoring advice: "At each HTTP endpoint, verify that the expected CRL file is current, complete, and readable by the intended clients." Compare the CA's file with what the web server hands out:
# Read-only. Same bytes on the CA and at the HTTP CDP? (file names are examples; C:\Temp\issuing.crl is Step 3's download)
Get-FileHash -Path "$env:SystemRoot\System32\CertSrv\CertEnroll\ContosoIssuingCA.crl", 'C:\Temp\issuing.crl' -Algorithm SHA256 |
Select-Object Hash, Path
Different hashes with a current CA file means the copy job, the share, or the replication between web servers didn't run (our reading). Microsoft's lab guide grants Cert Publishers modify rights on the web server's pki folder so the CA can write there; its 0x80092013 article adds "check File System Permissions on the CRL and Delta CRL files."
Delta CRLs and IIS. A delta CRL's file name carries a +. Microsoft's lab guide: "Allowing double escaping is needed if you are publishing Delta CRLs to IIS because the Delta CRL file contains a + symbol." IIS's request filtering reference: "If set to true, request filtering will allow URLs with doubly-escaped characters." and "The default value is false." Read the setting on the CDP's site or folder:
# Read-only. On the IIS server: double escaping on the CDP folder (site and folder names are examples)
Import-Module WebAdministration
Get-WebConfigurationProperty -PSPath 'IIS:\Sites\Default Web Site\pki' -Filter system.webServer/security/requestFiltering -Name allowDoubleEscaping
Microsoft's CDP article also asks for "the .crl MIME mapping required by your web-server policy" and for checking "the actual HTTP response after deployment". Step 3's Invoke-WebRequest download is that check for the base CRL; repeat it with the delta file name (the + one) from Step 2's Base CRL CDP line.
| Finding | What it means (our reading unless noted) | Next |
|---|---|---|
| Hashes differ | The web copy is stale | Step 11: the copy or replication job |
| Base downloads, delta returns 404 | Double escaping off, or the delta was never copied | Step 11: double escaping (Microsoft's lab form) |
| 401 or 403 from the CDP URL | Anonymous read missing on the folder or site | Folder permissions |
CRL downloads, certutil -dump can't parse it |
The server returns something else (an error page, a captive portal) | The response itself |
Step 6: Turn on CAPI2 logging and read the action that failed (read-only, one log switch)
When -urlfetch passes and the app still fails, the CAPI2 log shows what the app's own validation did. Microsoft's CAPI2 guide on why the app's message isn't enough: the API "was designed to return an error code and string", so many network problems reach the app as revocation offline. Enable the log, make it bigger, reproduce once, export, and turn it off. Microsoft: "The default size for the event log is 1 MB." It recommends "at least 4 MB to capture relevant events".
:: Low-risk. Enable CAPI2 logging at 4 MB, reproduce the failure, export, then disable (Microsoft's wevtutil forms)
wevtutil sl Microsoft-Windows-CAPI2/Operational /ms:4194304
wevtutil sl Microsoft-Windows-CAPI2/Operational /e:true
:: reproduce the failure now, then:
wevtutil epl Microsoft-Windows-CAPI2/Operational C:\Temp\capi2.evtx
wevtutil sl Microsoft-Windows-CAPI2/Operational /e:false
# Read-only. CAPI2 errors, then the ones whose XML names a known revocation action (action list from Microsoft's guide; the filter is ours)
$capi = Get-WinEvent -LogName 'Microsoft-Windows-CAPI2/Operational' -MaxEvents 2000 -ErrorAction SilentlyContinue
$capi | Where-Object Level -eq 2 | Select-Object TimeCreated, Id, TaskDisplayName | Format-Table -AutoSize
$capi | Where-Object { $_.ToXml() -match 'CheckTimeValidity|CanRetrieveFromNetwork|CanCheckServerForUpdatedCrl|BadProxy|GetCrlOrOcspUrls|IsCrlSignatureValid|2EE7|2EFD' } |
Select-Object TimeCreated, Id, TaskDisplayName
Microsoft's guide names the events to open: results are in CertVerifyRevocation, and "To find the cause, you need to look at the" CertRejectedRevocationInfo and CryptRetrieveObjectByUrlWire events nested under it. In CertGetCertificateChain, the status flags CERT_TRUST_REVOCATION_STATUS_UNKNOWN plus CERT_TRUST_IS_OFFLINE_REVOCATION mean the revocation information couldn't be retrieved or was invalid. The actions, each from Microsoft's guide:
| Action or code in the event | Microsoft's meaning | Link in the chain |
|---|---|---|
CheckTimeValidity (CertRejectedRevocationInfo) |
The CRL is expired: now is past its nextUpdate | Steps 3, 9 |
IsCrlSignatureValid |
"If the signature of the CRL cannot be verified" | The CRL file at that location is wrong or corrupt (Step 5) |
GetCrlOrOcspUrls |
No CDP and no OCSP URL in the certificate | Step 8 |
CanRetrieveFromNetwork |
Back-off: "CAPI2 avoids contacting the server too often for repeated offline responses." | Wait for EarliestOnlineTime, or Step 7 |
BadProxy (CryptRetrieveObjectByUrlWire) |
The proxy is wrong; usually the first in the list | Step 4 |
2EE7 |
ERROR_WINHTTP_NAME_NOT_RESOLVED |
DNS for the CDP or proxy host |
2EFD |
ERROR_WINHTTP_CANNOT_CONNECT |
The CDP host's port, or the host is down |
Microsoft's summary for the network rows: "fix the underlying component (such as network, proxy settings, Web server, or LDAP) that is causing failure."
Step 7: Is the client using a cached old CRL? (read-only)
Clients keep CRLs. Microsoft's 0x80092013 article: "The CRL is retrieved by clients who can then cache the CRL (based on the configured lifetime of the CRL)". So a CRL you republished an hour ago doesn't help a client that cached the old one, and its fourth cause is exactly that: "The CRL is reachable but the client is picking from an old cache." Read the cache before you clear it:
:: Read-only. Cached CRL URLs for the current user's context (certutil's CRL filter), saved for the ticket
certutil -urlcache CRL > C:\Temp\urlcache-crl.txt
Microsoft's syntax note for -URLCache: delete "deletes relevant URLs from the current user's local cache." Run the read in the same context as the failure: as the user for a user app, and as SYSTEM (Step 4's PsExec form) for a service (our reading).
CAPI2's back-off is a second kind of cache. After repeated offline results it stops asking; the CanRetrieveFromNetwork event gives the EarliestOnlineTime it will try again (Step 6).
| Finding | What it means (our reading unless noted) | Next |
|---|---|---|
| Cached CRL for the failing URL, and the server copy is newer | Client still holds the old one | Step 11: Microsoft's two cache commands, after the server side is fixed |
| No cached entry, still failing | Not a cache problem | Steps 4, 5 |
CanRetrieveFromNetwork in CAPI2 |
Back-off after repeated failures | Fix the URL first, then Step 11 |
Step 8: The CA side: publication, schedule, and CDP settings (read-only)
Did the CA publish? Microsoft's AD CS event list gives the CRL publication events in the Application log:
| Event | Microsoft's text (event list for AD CS) | Means (our reading) |
|---|---|---|
| 48 | "Revocation status for a certificate in the chain for CA certificate %3 for %1 could not be verified because a server is currently unavailable." | The CA can't check its own chain (usually the parent's CRL) |
| 49 | "could not be verified because no information is available describing how to check the revocation status." | A certificate in the CA's chain has no usable CDP |
| 65 / 66 | "Active Directory Certificate Services could not publish a Base CRL for key %1 to the following location: %2." (66: Delta) | A publication location refused the write: path, share, or LDAP rights |
| 67 | "made %1 attempts to publish a CRL and will stop publishing attempts until the next CRL is generated." | Retries ran out; that location stays stale until the next CRL |
| 74 / 75 | The same base / delta failure "to the following location on server %4" | Same, for a named server |
| 100 | "Active Directory Certificate Services did not start: Could not load or verify the current CA certificate." (Microsoft's Event 100 page) | The CA won't start; the CRL for a CA above it is the common cause |
# Read-only. On the CA: chain, publication, and startup events, last 30 days (IDs from Microsoft's AD CS event list)
Get-WinEvent -FilterHashtable @{
LogName = 'Application'; ProviderName = 'Microsoft-Windows-CertificationAuthority'
Id = 48, 49, 65, 66, 67, 74, 75, 100; StartTime = (Get-Date).AddDays(-30)
} -ErrorAction SilentlyContinue | Select-Object TimeCreated, Id, Message | Format-List
# Read-only. Is Certificate Services running?
Get-Service -Name CertSvc | Select-Object Name, Status, StartType
A Microsoft Q&A post shows what a failed LDAP publish looks like in practice: "could not publish a Base CRL for key 0 to the following location: ldap:///" with a logon failure code (community; the location is redacted in the post).
Where does the CA publish, and what do certificates carry? Microsoft: the CA's CDP configuration "represents both publication and retrieval settings". Read both:
# Read-only. On the CA: every CDP and AIA entry with its flags (Microsoft's lab guide uses the same Format-List form)
Get-CACrlDistributionPoint | Format-List
Get-CAAuthorityInformationAccess | Format-List
Microsoft's cmdlet page lists the flags on each CDP entry: PublishToServer, PublishDeltaToServer, AddToCertificateCdp, AddToFreshestCrl, AddToCrlCdp, AddToCrlIdp, and Uri. PublishToServer is where the CA writes; AddToCertificateCdp is what clients get told. A URL with AddToCertificateCdp and nothing that copies the file there is a CDP that goes stale on its own (our reading).
The trap with CDP changes: "Changing a CDP URL affects only certificates issued after the change." Microsoft's design page says the same: "Modifying the URL for a CRL distribution point only affects newly issued certificates." Certificates issued before a change still point at the old URL. Microsoft's troubleshooting table for that case: "Keep the old CDP online and restore current CRL content there. A CDP configuration change doesn't repair old certificates."
The CA's own startup. Microsoft's Event 100 page: AD CS "startup can fail if there are problems with availability, validity, and chain validation for the CA certificate." Its resolution includes: "If a CRL for a CA in the chain has expired, generate new base and delta CRLs on this CA and copy them to the required locations." Microsoft's lab guide is blunt about the subordinate: if it can't find its parent's CRLs, the service "will fail to start on the subordinate CA". For the issuing CA, the parent is usually the offline root, so Step 9 is the fix. Read the CA's current ignore flags before anyone changes them:
:: Read-only. On the CA: CRLFlags as set now (a stopgap someone set last year shows up here)
certutil -getreg ca\CRLFlags
| Finding | What it means (our reading unless noted) | Next |
|---|---|---|
| Events 65/74 for an LDAP location | The CA can't write its CDP object | Rights on the CDP container; Step 11 |
| Events 65/74 for a file or UNC location | Share, NTFS, or path problem | The share and Cert Publishers rights (Step 5) |
| Event 100 or 48 at startup | The parent CA's CRL is expired or unreachable from the CA | Step 9 |
CRLF_REVCHECK_IGNORE_OFFLINE already set |
A stopgap from an earlier outage, hiding offline results on the CA | Fix the CRL, then remove it (Step 11) |
| CDP URL in certificates differs from where the CA publishes | Old certificates point at a location nobody updates | Keep the old location current (Microsoft) |
No HTTP URL with AddToCertificateCdp
|
Off-network and non-Windows clients can't fetch | Step 11: staged CDP add for new certificates |
Step 9: The offline root's CRL (read-only)
The offline root is the one CA nobody turns on, so its CRL is the one that expires. Microsoft's CDP article: "For an offline root CA, avoid delta CRLs unless a documented requirement justifies them. Use a scheduled, witnessed process to publish a new base CRL, transfer it to every retrieval endpoint, and verify it from the networks that depend on the root." Microsoft's lab guide sets the root up that way in its CAPolicy.inf: CRLPeriod=weeks, CRLPeriodUnits=26, CRLDeltaPeriodUnits=0, and explains: "Setting the CRLDeltaPeriodUnits=0 in the CAPolicy.inf disables Delta CRL publishing, which is the appropriate setting for an offline Root CA."
Read the root CRL's dates without powering on the root. Step 3's download of the root CRL gives NextUpdate; so does the issuing CA's block in Step 2's output. Then read the copy the issuing CA keeps locally. Microsoft's lab guide adds the root CRL to the subordinate's local Root store during setup; -store lists that store's contents (our reading that the CRL entries show there):
:: Read-only. On the issuing CA: CRLs in the local machine Root store (where the lab guide's addstore puts the root CRL)
certutil -store Root > C:\Temp\root-store.txt
A community answer on Microsoft Q&A notes a stale root CRL in that store can keep a service failing after the HTTP copy is fixed. The same answer adds that "Things like MSCEP" (NDES) read it from there; if Intune SCEP broke at the same time, Intune SCEP Certificate Profile Failed covers the NDES side.
Two clock checks before you blame the CRL (our reading): the root's clock sets ThisUpdate and NextUpdate on the CRL it signs, and a root VM that boots once a year can come up with the wrong time. A client clock that's off by days can call a valid CRL expired, too. Windows Time Not Syncing With Domain Controller covers the domain clients' side.
| Finding | What it means (our reading unless noted) | Next |
|---|---|---|
| Root CRL past NextUpdate | Everything under the root fails revocation | Step 11: the offline root sequence |
| Root CRL valid for days, not months | The CRL period is short for a CA that's off | Step 11: raise it on the root, then publish |
| HTTP root CRL current, issuing CA's store copy old | The CA validates with the old copy | Step 11: addstore, then cache resync |
| Root CRL's ThisUpdate is in the future or far past | The root's clock was wrong when it signed | Fix the root's time, then republish |
Step 10: What each relying party does with "unknown" (read-only)
The same expired CRL looks different in each app. Read each app's policy before you change it, so you know what you'd be turning off:
NPS (802.1X Wi-Fi and VPN, EAP-TLS). Microsoft's NPS page lists four DWORDs under HKLM\SYSTEM\CurrentControlSet\Services\RasMan\PPP\EAP\13, and "The following keys are set to 0 by default." With IgnoreRevocationOffline at 0: "When the NPS can't connect to a server that stores a revocation list, the certificate fails the revocation check and authentication fails." Read them on every NPS server:
# Read-only. On each NPS server: the EAP-TLS revocation keys (missing = default 0)
Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\RasMan\PPP\EAP\13' -ErrorAction SilentlyContinue |
Select-Object IgnoreNoRevocationCheck, IgnoreRevocationOffline, NoRevocationCheck, NoRootRevocationCheck
Microsoft's 802.1X troubleshooting page puts revocation in its list of usual causes: "Examples include invalid certificate, expiration, chain verification failure, and revocation check failure."
Smart card and certificate logon. Microsoft's smart card article: "The revocation check must succeed from both the client and the domain controller." It also requires that "Every CA Certificate except the root CA in the certificate chain contains a valid CDP extension in the certificate." The client-side code for the DC's certificate is 0x80090358 (Step 1). Microsoft's DC certificate requirements: "The certificate must have a CRL distribution-point extension that points to a valid certificate revocation list (CRL)." Run Step 2 on a DC's certificate from a client, and on the client's certificate from a DC.
DirectAccess. Microsoft's article is the 0x80092013 source above; its read is netsh interface http show interface on the client.
SharePoint and other apps that call out to public CRLs. Microsoft's SharePoint article describes the slow version: "If the online CRL server cannot be reached from the SharePoint server for some reason, the operation times out after 15 seconds by default." No error, just slow pages (our reading of that article for internal apps).
curl and Git on Windows. Both use Schannel, Windows' own validation, so an internal or proxy-inspected certificate whose CDP isn't reachable fails there too, often as 0x80092012 (our reading). curl's manual describes --ssl-revoke-best-effort: "Ignore certificate revocation checks when they failed due to missing/offline distribution points for the revocation check lists." and warns about --ssl-no-revoke: "WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that." Git's http.schannelCheckRevoke: "Only necessary to disable this if Git consistently errors and the message is about checking the revocation status of a certificate." Run Step 2 on the server certificate first; if a TLS-inspecting proxy re-signs traffic, the CDP that fails is the proxy CA's (our reading).
| Finding | What it means (our reading unless noted) | Next |
|---|---|---|
| NPS keys all 0 or missing | Default: NPS fails closed when a CRL is unavailable | Fix the CRL (Steps 3-9) |
NoRevocationCheck = 1 already |
Revocation is off on that NPS server; revoked certificates connect | Find who set it and why, then plan to remove it |
| Smart card logon fails, password logon works | DC or client can't check the other's chain | Step 2 in both directions |
| Only curl / Git fail | Schannel can't reach the CDP of that certificate's issuer | Step 2 on the server certificate; Step 4 |
Common false starts (read these before you change anything)
| False start | Why it hurts | Read-first alternative |
|---|---|---|
CRLF_REVCHECK_IGNORE_OFFLINE on the CA, left on |
Community answers on Microsoft Q&A use it to get a CA started; left on, the CA stops caring whether its chain's CRLs are reachable (our view) | Step 9's root CRL first; if you must use it, Step 11's time-boxed form |
NoRevocationCheck = 1 on NPS |
Microsoft: with it enabled, NPS "prevents EAP-TLS from performing a revocation check of the certificate of the client." A revoked laptop certificate connects | Fix the CRL; if you need a bridge, Step 11 |
| Revocation off in the VPN or app profile | Every client now needs a second change to undo it (our view) | Step 10 for that app |
| Changing the CDP URL on the CA to repair old certificates | Microsoft: "Changing a CDP URL affects only certificates issued after the change." | Restore current content at the old URL (Step 8) |
| Republishing on the CA and declaring it fixed | Publishing isn't delivering; clients may hold the old one | Steps 5, 7 |
| Renewing the CA certificate | A new CA certificate doesn't renew the parent's CRL that failed (our reading) | Step 9 |
certutil -urlcache * delete on every machine, repeatedly |
Clears the symptom for an hour if the server copy is still old (our reading) | Fix the server copy, then clear once |
| Delta CRLs on the offline root | Microsoft: "Avoid publishing delta CRLs on offline root CAs." Each one means powering on the root | Base CRL only on the root (Step 9) |
Step 11: Change one link, then prove it (only after the read)
Pick the one change Steps 1-10 point at. Save the current state first:
# Low-risk write. Save the CA configuration, CDP/AIA settings, and current CRL files (our habit; run on the CA)
New-Item -Path C:\Temp\pki-before -ItemType Directory -Force | Out-Null
reg export 'HKLM\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration' C:\Temp\pki-before\certsvc-config.reg /y
Get-CACrlDistributionPoint | Format-List > C:\Temp\pki-before\cdp.txt
Get-CAAuthorityInformationAccess | Format-List > C:\Temp\pki-before\aia.txt
Copy-Item -Path "$env:SystemRoot\System32\CertSrv\CertEnroll\*.crl" -Destination C:\Temp\pki-before
Expired issuing CA CRL (Step 3). On the CA, Microsoft's scripted form:
:: Change, on the issuing CA. Publish new base (and delta, if configured) CRLs, then read the status back
certutil -config "CA01.contoso.com\Contoso Issuing CA" -CRL
certutil -CAInfo crlstatus
Microsoft: certutil -CRL "Publishes new certificate revocation lists (CRLs) or delta CRLs." Then deliver it. If no job copies the file, copy it to every HTTP location yourself (Microsoft's server-certificate guide uses a plain copy to the web server's share), and rerun Step 5's hash check:
# Change. Copy the CA's CRLs to the web server's pki share (share name is an example; Microsoft's guide uses \\WEB1\pki)
Copy-Item -Path "$env:SystemRoot\System32\CertSrv\CertEnroll\*.crl" -Destination '\\WEB01\pki' -WhatIf
Expired offline root CRL (Step 9). Microsoft's Event 100 page and lab guide, in order (the order and the dry-run habits are ours):
- Power on the root. Check its clock before anything else (Step 9).
- If the CRL period is too short for how often you'll do this, change it first. Microsoft's lab guide sets the root's values with
certutil -setreg, restarts the service, and publishes; one example:
# Change, on the offline root (elevated PowerShell). Longer CRL validity, 12-hour overlap, restart, publish (Microsoft's lab form; values are examples)
certutil -setreg CA\CRLPeriodUnits 26
certutil -setreg CA\CRLPeriod "Weeks"
certutil -setreg CA\CRLOverlapPeriodUnits 12
certutil -setreg CA\CRLOverlapPeriod "Hours"
Restart-Service -Name CertSvc
certutil -crl
- Copy the new CRL from
%windir%\System32\CertSrv\CertEnrollto removable media, then power the root off. - On a domain-joined machine, publish it to AD.
-dspublish"Publishes a certificate or certificate revocation list (CRL) to Active Directory." and "Use -f to create a new DS object." Its container argument "is the DS CDP container CN, usually the CA machine name." - Copy it to every HTTP CDP location, and add it to the issuing CA's local store the way Microsoft's lab guide does.
- Rerun Step 2 on the issuing CA's certificate, then start the issuing CA if it was down.
:: Change, on a domain member (the issuing CA works). Root CA machine name ORCA1 and file names are examples
certutil -f -dspublish C:\Temp\ContosoRootCA.crl ORCA1
copy C:\Temp\ContosoRootCA.crl \\WEB01\pki\
certutil -addstore -f root C:\Temp\ContosoRootCA.crl
certutil -verify -urlfetch C:\Temp\issuing-ca.cer
Microsoft's lab guide on that last store step: "The second and third commands place the root CA certificate and CRL into the local store of APP1." Then put the next NextUpdate, minus the overlap, on a calendar with an owner. Microsoft: "Alert before a CRL reaches its next-update boundary."
A CA that won't start while you fix the root (Step 8). The fix is the parent's CRL. If issuance has to resume first, community answers on Microsoft Q&A use the ignore-offline flag, then remove it once the CRL is valid (the accepted answer also resyncs the chain cache before removing it). Treat it as a time-boxed exception with an owner (our view):
# Change, on the issuing CA (stopgap, elevated PowerShell). Read, set, restart; flag form from Microsoft Q&A community answers
certutil -getreg ca\CRLFlags
certutil -setreg ca\CRLFlags +CRLF_REVCHECK_IGNORE_OFFLINE
Restart-Service -Name CertSvc
# After the root CRL is fixed and Step 2 passes on the CA: resync the chain cache, remove the flag, restart
# ('@now' is quoted because PowerShell would read a bare @now as splatting)
certutil -setreg chain\ChainCacheResyncFiletime '@now'
certutil -setreg ca\CRLFlags -CRLF_REVCHECK_IGNORE_OFFLINE
Restart-Service -Name CertSvc
Unreachable CDP (Step 4). One change for the one cause: a DNS record, a firewall rule from that network to the CDP host on TCP 80, or a proxy bypass for the CDP host so SYSTEM doesn't need proxy credentials. Then rerun Step 4 from that network and context.
Stale web copy or delta 404 (Step 5). Fix the copy job first. For deltas on IIS, Microsoft's 0x80092013 article gives the double-escaping setting; dry-run it on the CDP folder only:
# Change, on the IIS server. Allow double escaping on the CDP folder only (site and folder are examples; Microsoft's KB form plus -WhatIf)
Set-WebConfiguration -Filter system.webServer/security/requestFiltering -PSPath 'IIS:\Sites\Default Web Site\pki' -Value @{ allowDoubleEscaping = $true } -WhatIf
Cached old CRL (Step 7). Only after the server copy is current. Microsoft's two commands, from the 0x80092013 article and the NPS page ("To manually update the CRL on your NPS server, run these commands in the command prompt or PowerShell"), in the context that fails:
:: Change (local caches only). Clear cached URLs and force chain revalidation; run as the failing context (SYSTEM via psexec -s for services)
certutil -urlcache * delete
certutil -setreg chain\ChainCacheResyncFiletime @now
CDP changes for new certificates (Step 8). Microsoft's article is the procedure: "Use a staged transition. Add and validate the new endpoint before you remove an old one." Add-CACrlDistributionPoint "prompts for confirmation". For removal with Remove-CACrlDistributionPoint, use -WhatIf and the matching flags: "If you omit the flags, the cmdlet removes every distribution point that matches the URI."
# Change, on the CA. Add an HTTP URL to the CDP of newly issued certificates (Microsoft's example; prompts unless -Force)
Add-CACrlDistributionPoint -Uri "http://pki.contoso.com/pki/<CAName><CRLNameSuffix><DeltaCRLAllowed>.crl" -AddToCertificateCdp -AddToFreshestCrl
Restart the CA if the cmdlet says so, publish new CRLs, issue one test certificate, and run Step 2 on it.
A bridge on NPS while the CRL is fixed (Step 10). Microsoft documents IgnoreRevocationOffline: "When enabled, the NPS allows EAP-TLS clients to connect even when a server that stores a CRL isn't available on the network". It's narrower than NoRevocationCheck: a certificate that's on a CRL NPS can read is still rejected (our reading). If you use it, use it on purpose, write down who owns setting it back to 0, and do it on every NPS server the same way (our view).
Then prove it. Microsoft's validation step: "Confirm that the output shows a successful retrieval of the base CRL and, when you use delta CRLs, of the associated delta CRL." From each network zone, as the user and as SYSTEM:
:: Read-only (writes local files). Step 2 again from each zone and context; then confirm with the app itself
certutil -verify -urlfetch C:\Temp\failing.cer > C:\Temp\verify-after.txt
psexec -s cmd /c "certutil -verify -urlfetch C:\Temp\failing.cer > C:\Temp\verify-after-system.txt"
Then the thing the user waited for: one laptop on the 802.1X SSID, one VPN connection, one smart card logon. Turn CAPI2 logging off if it's still on (Step 6), and remove any stopgap flag you set (our habit).
When a change is justified
| Finding | Prefer | Avoid |
|---|---|---|
| Root CRL expired | Root on, certutil -crl, publish to AD and HTTP, store copy on the issuing CA |
Ignore flags on every relying party |
| Issuing CRL stale at HTTP | Fix the copy job; hash-check the web copy | Republishing again and again on the CA |
| CDP unreachable from one zone | DNS, firewall, or proxy bypass for that host | Turning off revocation for that zone's clients |
| Client still on the old CRL | Clear its cache once, after the server is current | Scheduled cache wipes on every machine |
| CA down during the root fix | Time-boxed CRLF_REVCHECK_IGNORE_OFFLINE, then remove it |
Leaving it set after the CRL is fixed |
| Old certificates with a dead URL | Keep that URL serving current CRLs | Changing the CA's CDP and expecting old certificates to follow |
FAQ
What does "the revocation server was offline" mean?
A client building a certificate chain couldn't get valid revocation information for one of the certificates: usually an expired CRL, a CDP URL it can't reach in its context, a CRL that was never published, or an old cached copy. Those are the four causes in Microsoft's 0x80092013 article. The error code is 0x80092013, CRYPT_E_REVOCATION_OFFLINE.
How do I find which CRL is expired?
Run certutil -verify -urlfetch on the failing certificate from the failing machine, then read NextUpdate on each CRL it fetched. Download the CRL from its HTTP URL and run certutil -dump on it to confirm. The offline root's CRL is the first one to check.
How do I renew an offline root CA's CRL?
Power on the root, run certutil -crl, copy the new CRL off on removable media, publish it to AD with certutil -f -dspublish, copy it to every HTTP CDP location, and add it to the issuing CA's store with certutil -addstore -f root. Then verify from the issuing CA with certutil -verify -urlfetch, and set a reminder before the next NextUpdate.
Why does it still fail after I published a new CRL?
Publishing on the CA doesn't copy the file to the web server, and clients keep cached CRLs. Compare the web copy's hash with the CA's file, then clear the failing context's cache with certutil -urlcache * delete and certutil -setreg chain\ChainCacheResyncFiletime @now.
What's the difference between 0x80092012 and 0x80092013?
0x80092013 is "revocation server was offline": revocation information exists but couldn't be retrieved or isn't valid. 0x80092012 is "unable to check revocation for the certificate", which is common when a certificate has no usable CDP at all, or from curl and Git on Windows. Both start with Step 2.
Related posts
- Certificate Autoenrollment Not Working (the AD CS sibling: autoenrollment's revocation error is Step 9 there)
- Intune Wi-Fi Profile Not Applying (EAP-TLS Wi-Fi, often the first thing an expired CRL breaks)
- Intune SCEP Certificate Profile Failed (NDES and the connector depend on the same chain)
- Windows Time Not Syncing With Domain Controller (a wrong clock can make a valid CRL look expired)
Free checklist
An ignore-revocation flag pushed to every NPS server before one laptop was tested against a fixed CRL is a change nobody tested where the validation happens. So is a Conditional Access policy enforced before anyone read the report-only results. The free Conditional Access pre-enforcement checklist keeps break-glass accounts, pilot groups, and report-only review on one page:
If you want the rest of the Intune runway written down for the team (enrollment hygiene, compliance phasing, break/fix cards and read-only PowerShell snapshots), the Intune & M365 Admin Starter Pack pairs with this triage: https://cashflow4375.gumroad.com/l/joonf?utm_source=devto&utm_medium=article&utm_campaign=aps_first_sale&utm_content=revocation-server-offline-crl-expired
Admin Pack Studio; not affiliated with Microsoft. Steps 1-10 favor read-only checks: certutil -verify -urlfetch, -ca.cert, -dump, -URL, -CAInfo, -getreg, -dsCRL, -urlcache CRL, -store, Select-String, Invoke-WebRequest, Get-ChildItem, Get-ADRootDSE, Get-ADObject, Resolve-DnsName, Test-NetConnection, netsh winhttp show proxy, reg query, Get-FileHash, Get-WebConfigurationProperty, Get-WinEvent, Get-Service, Get-CACrlDistributionPoint, Get-CAAuthorityInformationAccess, and Get-ItemProperty. Output redirection, downloads, wevtutil epl, and reg export write local files; psexec -s runs the same reads as SYSTEM. CAPI2 logging, certutil -CRL, CRL period and overlap values, -dspublish, -addstore, file copies to CDP locations, CRLFlags, Restart-Service CertSvc, cache clears, IIS double escaping, proxy and firewall changes, CDP changes, and NPS registry keys are deliberate changes. Change one link at a time, test from each network zone and context, and check current Microsoft Learn for version-specific behavior.
Top comments (0)