The ticket usually starts as "Wi-Fi is down on the third floor." An hour later the VPN is failing for half the remote staff, and someone in finance can't log in with their smart card. You check the RADIUS server's certificate: valid for another ten months. You pull a client certificate. Valid, chains to a trusted root, not revoked. Everything you check comes back fine, and nothing works.
Somewhere on a file share or a forgotten IIS box, a certificate revocation list has passed its nextUpdate time. Every Windows component that checks revocation by default is now rejecting certificates from your entire PKI.
A CRL is a signed document with its own expiry
RFC 5280 gives every CRL two timestamps. thisUpdate is when the CRL was issued. nextUpdate is the latest time the next one will be issued, and section 5.1.2.5 requires conforming CAs to include it. Clients treat nextUpdate as an expiry date. Once it passes, the CRL no longer describes the present and the client has to go get a new one.
AD CS adds a third timestamp: a Microsoft-specific Next CRL Publish extension, which is when the CA plans to publish the next one. nextUpdate sits a little later. The gap between the two is the overlap period, and it exists to absorb replication and caching delays. If you never set CRLOverlapPeriod yourself, the CA uses 10% of the CRL period, capped at 12 hours. Remember that 12-hour cap. It comes back later.
The part that hurts is what a client does when it can't find a current CRL. The validation algorithm in RFC 5280 section 6.3.3 doesn't fall back to "not revoked." It returns UNDETERMINED, and a client that fails closed treats undetermined the same as revoked. On Windows that shows up as CRYPT_E_REVOCATION_OFFLINE (0x80092013): "The revocation function was unable to check revocation because the revocation server was offline."
I've watched that message send engineers off hunting for firewall rules. The server usually isn't offline. The HTTP fetch returned a 200 with a well-formed CRL whose nextUpdate is in the past, and CryptoAPI treats a stale CRL exactly like no CRL at all.
Worse, the stale CRL often isn't the one for the leaf you're staring at. Windows checks revocation for every certificate in the chain, so the issuing CA's own certificate gets checked against the root's CRL. When the root CRL expires, every leaf under every issuing CA fails, even if the issuing CAs are publishing on time.
The fastest diagnosis I know is certutil -verify -urlfetch against a failing leaf. It lists every CDP URL for every certificate in the chain along with its status. The expired one is easy to spot.
How CRLs go stale
The offline root
In the textbook two-tier AD CS design, the root CA stays powered off. Its CRL gets a long validity, usually six months to a year, because a person has to bring the root online to sign a new one. In practice the ceremony goes like this. You find the VM or the physical box. You discover the clock has drifted or the local admin password has expired. You track down whoever holds the HSM operator cards or the PIN. You publish the CRL, carry the file off on removable media, upload it to every HTTP CDP and push it into AD with certutil -dspublish. This happens twice a year, and the person doing it often joined the company after the last time anyone did it.
The usual failure: the person who built the PKI left, the calendar reminder lived in their mailbox, and the runbook is a Word document on a SharePoint site that has since been migrated. Then the root CRL expires on a Saturday.
Issuing CAs after a migration
Online issuing CAs publish on their own schedule (a weekly base CRL and a daily delta by default), so people assume they're safe. Then the CA moves to a new server. The key and database get restored, the service starts, and certificates issue normally. But the CDP publication paths still point at a UNC share on the old web server, and the new machine account can't write there. The CA drops the CRL in its local CertEnroll folder and logs a publish failure that nobody reads. The HTTP CDP keeps serving last week's file until it expires.
A decommissioned CA fails the same way. Its certificates stay valid for two more years, but the CA service is gone and nothing publishes. The standard advice is to publish one final CRL before shutdown, with a validity that outlasts the last certificate the CA issued. Plenty of teams skip it.
LDAP-only CDPs
The default AD CS CDP list includes an ldap:/// path into the Configuration partition. Domain-joined machines on the corporate network resolve it fine. Entra-joined laptops, Macs, Linux hosts and network gear can't. Microsoft's Windows Hello for Business docs say so outright: Entra-joined devices that authenticate against on-premises domain controllers need an HTTP CDP for the domain controller certificate's CRL, because they can't read AD. If your certificates carry only LDAP CDPs, part of your fleet has never been able to check revocation. You won't find out until one of those devices hits a component that fails closed.
The HTTP host nobody owns
pki.corp.example.com points at an IIS server someone set up once, and no team is sure it's theirs. A DNS cleanup removes the record. Or someone forces HTTPS on the site, so now a client has to validate the CDP's own TLS certificate, revocation included, before it can download the CRL. That's why CDPs should be plain HTTP. Or a rebuilt server loses the IIS request filtering setting that allows double escaping, and delta CRLs, whose filenames contain a plus sign, start returning 404. Or a load balancer sits in front of two web servers and only one of them got the new file. Any one of these is enough.
Which clients actually notice
These Windows components fail closed by default:
- NPS, which handles 802.1X Wi-Fi and Always On VPN with EAP-TLS or PEAP-TLS. The overrides are registry values under HKLM\SYSTEM\CurrentControlSet\Services\RasMan\PPP\EAP\13: IgnoreRevocationOffline, IgnoreNoRevocationCheck and NoRevocationCheck. They're the usual 2am fix. If you set them, open a ticket to remove them and put a date on it, or your revocation checking stays advisory forever.
- Smart card logon, which checks in both directions. The KDC checks the user's certificate and the client checks the domain controller's, which gets you the memorable "the revocation status of the domain controller certificate used for smartcard authentication could not be determined."
- IIS client certificate authentication. HTTP.sys checks client certificate revocation by default, and the netsh http show sslcert output lists Verify Client Certificate Revocation as Enabled.
Most other clients never check:
- Chrome and Edge don't do online revocation checks for ordinary certificates, and CRLSets only cover public CAs. The exception is the RequireOnlineRevocationChecksForLocalAnchors enterprise policy, which makes them hard-fail for chains that end in a locally installed root. Check your GPOs for it.
- Firefox doesn't fetch CRLs from CDPs.
- OpenSSL, curl, Go's crypto/x509 and most language runtimes never fetch CDPs. Java leaves revocation checking off unless you turn it on.
So you get an outage you can't reproduce from your laptop. curl on a Mac says everything's fine, and your Linux-based synthetic probe stays green. The failures also trickle in as Wi-Fi sessions reauthenticate and people arrive and log in, so the first hour looks like random flakiness instead of one cause.
The same trap outside Windows
Vault PKI
Vault's CRL expiry defaults to 72 hours. auto_rebuild defaults to false, and in that mode Vault only rebuilds the CRL when a certificate is revoked or someone calls the rotate endpoint. A quiet mount with no revocations for three days serves a stale CRL. A sealed Vault rebuilds nothing. With auto_rebuild on, Vault rebuilds the CRL once it's within auto_rebuild_grace_period of expiry (12 hours by default). The catch is that revocations no longer trigger an immediate rebuild, so pair auto_rebuild with delta CRLs if revocation latency matters to you. Also make sure the CRL distribution URLs are set on the mount. Certificates issued before they were set have no CDP extension at all.
AWS Private CA
Private CA uses ExpirationInDays to set nextUpdate and writes the CRL to S3 through the acm-pca service principal. Publication stops if someone tightens the bucket policy, enables Block Public Access while the CA is still configured for public-read ACLs, or changes object ownership in a way that disables ACLs. AWS retries every 15 minutes. Meanwhile the old CRL sits in the bucket and keeps getting served until it expires. Private CA publishes a MisconfiguredCRLBucket metric in the AWS/ACMPrivateCA namespace. Set an alarm on it today. And if you change the CRL CNAME, AWS's own documentation warns that certificates issued before the change can no longer reach the CRL.
step-ca
CRL support in step-ca is off by default and still marked experimental. Once it's on, cacheDuration (24 hours by default) sets the next update and renewPeriod controls how often the CRL gets regenerated. The CA process generates the CRL itself, so if the CA is down longer than cacheDuration, the CRL expires.
Skipping revocation with short-lived certificates
step-ca's default model is passive revocation: certificates are short-lived, and revoking one just blocks its renewal. The CA/Browser Forum Baseline Requirements now let public CAs leave revocation information out of short-lived certificates, and Let's Encrypt shut down its OCSP service in 2025. If a certificate lives for a day, a CRL adds very little.
It isn't free, though. Your CA's availability becomes a hard dependency for every workload. A laptop that's been offline for two weeks comes back with a dead certificate. A stolen device stays trusted for the full certificate lifetime. Smart cards don't fit the model at all. For service-to-service mTLS, where workloads renew every few hours anyway, I'd take that trade. For anything a person carries around, I wouldn't.
Windows complicates it further. NPS rejects a certificate with no CDP by default (CRYPT_E_NO_REVOCATION_CHECK, 0x80092012), so dropping revocation for Wi-Fi certificates means setting IgnoreNoRevocationCheck. Do that on purpose, and write the decision down.
What to put in place
Build the CDP list from issued certificates, not from the CA configuration. Changing the CDP on the CA only affects new certificates. A CDP URL is effectively a promise that lasts as long as the longest-lived certificate that contains it. Pull CDP and AIA URLs from the AD CS database, Vault's certificate list, Private CA audit reports and your endpoint scans. Include the CA certificates themselves, since that's how you find the root CRL URL.
Fetch and parse on a schedule, from where your clients are. Run at least one check from outside the domain. Confirm that the URL returns a parseable CRL, that its signature verifies against the issuer, and that the CRL Number goes up over time. Every URL for the same issuer should serve the same CRL Number, delta CRLs included. Check that AIA URLs return the right issuer certificate.
Alert when a CRL is older than its publisher should ever let it get. Fixed day-count thresholds don't work. Take a weekly AD CS CRL with the default overlap. In normal operation its remaining validity runs from about 7.5 days down to 12 hours before the replacement shows up. A "3 days left" alert fires every single week, and someone mutes it within a month. When a publish actually gets missed, you have 12 hours. The signal you want is that Next CRL Publish has passed and the CDP is still serving the same CRL Number. For Vault, alert when the remaining time drops below the auto_rebuild grace period. For a 12-month root CRL, alert at half its validity, because getting the right people in a room for the ceremony takes weeks.
Give the root ceremony a calendar entry, a named owner and a backup owner. Republish at the midpoint of the CRL's validity. The runbook should cover where the box is, checking its clock, who holds the HSM PIN or cards, every CDP destination, and a final check from a non-domain machine. Walk through it once while nothing is broken.
Keep CRLs in the same inventory as your certificates. A CRL is a dated, signed object your chain depends on, same as an intermediate certificate. If you use CertPulse, the endpoint and chain checks already show which issuers your services depend on. Put each of those issuers' CRLs on the same list, with an owner and a threshold, so they don't end up in a separate tool nobody opens.
When your PKI goes down, the certificates are usually fine. What expired was a list nobody owned.
This is why we built CertPulse
CertPulse connects to your AWS, Azure, and GCP accounts, enumerates every certificate, monitors your external endpoints, and watches Certificate Transparency logs. One dashboard for every cert. Alerts when auto-renewal fails. Alerts when certs approach expiry. Alerts when someone issues a cert for your domain that you didn't request.
If you're looking for complete certificate visibility without maintaining scripts, we can get you there in about 5 minutes.