Kurz gesagt. Seit März 2024 verpflichtet das CA/Browser Forum die CAs, CRLs zu veröffentlichen, und macht OCSP optional. Let's Encrypt hat im Mai 2025 die OCSP-URLs aus seinen Zertifikaten entfernt und im August 2025 seine Responder abgeschaltet. Browser fragen bei den CAs nicht mehr für jedes einzelne Zertifikat nach. Firefox prüft einen lokalen CRLite-Filter, Chrome sein per Push verteiltes CRLSet, und Apple nutzt eine eigene aggregierte Liste. Die meisten Bibliotheken und Befehlszeilentools (Java, .NET, Go, Python, Node.js, curl) prüfen den Widerruf standardmäßig überhaupt nicht. In unseren Tests akzeptierten sie alle ein widerrufenes Zertifikat von Let's Encrypt. Wenn dein Client widerrufene Zertifikate ablehnen muss, aktiviere die CRL-Prüfung explizit und plane mit kurzen Zertifikatslaufzeiten. Auf kurze Laufzeiten steuert die Branche ohnehin zu.
Warum der Widerruf neu gedacht werden musste
Ein Zertifikat kann vor seinem Ablauf widerrufen werden, typischerweise weil sein Schlüssel abgeflossen ist oder es irrtümlich ausgestellt wurde. Die beiden klassischen Wege, Clients darüber zu informieren, waren:
- CRL (Certificate Revocation List, Zertifikatssperrliste, RFC 5280): Die CA veröffentlicht eine signierte Liste widerrufener Seriennummern. Das Zertifikat verweist in seiner Erweiterung CRL Distribution Points auf diese Liste.
- OCSP (RFC 6960): Der Client fragt den Responder der CA nach einem einzelnen Zertifikat und erhält eine signierte Antwort „good“, „revoked“ oder „unknown“. Die URL steht in der Erweiterung Authority Information Access.
Beide hatten im Web ihre Probleme. CRLs konnten groß werden. OCSP verriet der CA jede besuchte Website, erhöhte die Latenz, und Browser mussten auf „Soft-Fail“ ausweichen, wenn der Responder nicht erreichbar war. Ein Angreifer, der die OCSP-Anfrage blockieren kann, macht die Prüfung damit wertlos. OCSP-Stapling löste das Datenschutzproblem, indem der Server die Antwort selbst abruft und mitschickt, setzte sich aber nie flächendeckend durch. Must-Staple, ein Zertifikats-Flag, das eine per Stapling mitgelieferte Antwort verlangt, fand bei den Browsern kaum Unterstützung.
Was die Regeln heute vorschreiben
Ballot SC-063 („OCSP optional machen, CRLs verlangen und Automatisierung fördern“, wirksam ab dem 15. März 2024) hat die Baseline Requirements geändert:
- CRLs sind Pflicht. Eine CA muss innerhalb von 24 Stunden nach der Ausstellung ihres ersten Zertifikats eine vollständige oder in Shards aufgeteilte CRL veröffentlichen. Die CRL muss sie mindestens alle 7 Tage aktualisieren, wenn alle ihre Zertifikate eine OCSP-URL enthalten, andernfalls alle 4 Tage, und außerdem innerhalb von 24 Stunden nach dem Widerruf eines Zertifikats (§4.9.7). Bei Subscriber-Zertifikaten darf
nextUpdatehöchstens 10 Tage nachthisUpdateliegen (§7.2). - OCSP ist optional. Hat ein Zertifikat eine OCSP-URL, muss der Responder antworten, und seine Antworten müssen 8 Stunden bis 10 Tage gültig sein (§4.9.9). Ein Zertifikat ohne OCSP-URL muss einen CRL Distribution Point enthalten (§7.1.2.11.2).
- Kurzlebige Zertifikate brauchen keine Widerrufsinformationen. Ein „Short-lived Subscriber Certificate“ ist höchstens 7 Tage gültig, wenn es ab dem 15. März 2026 ausgestellt wird (davor 10 Tage). Die CA darf einen Widerruf dafür unterstützen, und der CRL Distribution Point ist optional.
- Fristen für den Widerruf. 24 Stunden bei Schlüsselkompromittierung und ähnlichen Fällen, 5 Tage für alle übrigen (§4.9.1.1).
Let's Encrypt hat OCSP daraufhin vollständig aufgegeben, „vor allem, weil es ein erhebliches Risiko für die Privatsphäre im Internet darstellt“:
- 30. Januar 2025: Neue Must-Staple-Anfragen schlagen fehl.
- 7. Mai 2025: OCSP-URLs werden aus neuen Zertifikaten entfernt, die stattdessen CRL-URLs enthalten.
- 6. August 2025: Die OCSP-Responder werden abgeschaltet.
Was Zertifikate heute enthalten
Eine Prüfung einiger Websites am 20. Oktober 2026 auf eine OCSP-URL, eine CRL-URL und eine per Stapling mitgelieferte OCSP-Antwort ergibt ein gemischtes Bild:
letsencrypt.org Let's Encrypt OCSP=- CRL=http://ye2.c.lencr.org/96.crl
staple: OCSP response: no response sent
www.google.com Google Trust Services OCSP=- CRL=http://c.pki.goog/we2/xuzt3PU9F_w.crl
staple: OCSP response: no response sent
github.com Sectigo Limited OCSP=http://ocsp.sectigo.com CRL=-
staple: OCSP response: no response sent
www.digicert.com DigiCert Inc OCSP=http://ocsp.digicert.com CRL=http://crl3.digicert.com/DigiCertEVRSACAG2.crl
staple: OCSP Response Status: successful (0x0)
www.microsoft.com Microsoft Corporation OCSP=http://oneocsp.microsoft.com/ocsp CRL=http://www.microsoft.com/pkiops/crl/partition/Microsoft%20TLS%20G2%20RSA%20CA%20OCSP%2004_Partition00093.crl
staple: OCSP Response Status: successful (0x0)
www.amazon.com Amazon OCSP=http://ocsp.r2m04.amazontrust.com CRL=http://crl.r2m04.amazontrust.com/r2m04.crl
staple: OCSP Response Status: successful (0x0)
Let's Encrypt und Google Trust Services stellen Zertifikate nur mit CRL aus. Das GitHub-Zertifikat von Sectigo ist der umgekehrte Fall – nur OCSP, was die BR erlauben. Ein Client, der nur eine der beiden Methoden unterstützt, kann nicht alle diese Zertifikate prüfen.
CRLs sind nicht klein, aber handhabbar:
http://yr1.c.lencr.org/46.crl 192226 bytes 5094 entries Oct 20 12:26:45 2026 GMT -> Oct 29 12:26:44 2026 GMT
http://c.pki.goog/we2/xuzt3PU9F_w.crl 87562 bytes 2456 entries Oct 20 11:12:29 2026 GMT -> Oct 30 10:12:28 2026 GMT
http://crl3.digicert.com/DigiCertEVRSACAG2.crl 635286 bytes 17091 entries Oct 20 06:24:08 2026 GMT -> Oct 27 06:24:08 2026 GMT
Let's Encrypt teilt seine CRLs in Shards auf: Jedes Zertifikat verweist auf eine von vielen nummerierten Dateien, und die Erweiterung Issuing Distribution Point der CRL nennt diesen Shard. Der CRL-Decoder liest jede dieser Dateien, prüft ihre Signatur anhand des Ausstellers und zeigt dir, ob ein bestimmtes Zertifikat darauf steht.
Wie Browser den Widerruf prüfen
Keiner der großen Browser sendet für ein gewöhnliches Zertifikat noch eine OCSP-Anfrage:
- Firefox: CRLite. Firefox lädt eine komprimierte Kodierung aller in CT-Logs erfassten widerrufenen Zertifikate herunter, aktualisiert sie alle 12 Stunden und prüft sie lokal. Laut Mozilla sind das im Schnitt etwa 300 kB Daten pro Tag. Seit Firefox 137 ist CRLite auf dem Desktop für alle aktiviert. Mit Firefox 142 entfiel OCSP für Zertifikate mit Domain-Validierung, und Firefox für Android hat CRLite mit Version 145 übernommen.
- Chrome: CRLSets. Chrome verteilt per Push eine Liste, die aus den CRLs der CAs erstellt wird, aber nur „eine Teilmenge der als widerrufen erkannten Zertifikate“ enthält, und „Online-Prüfungen (also OCSP und CRL) führt Chrome im Allgemeinen nicht durch“ (Chromium). In unserem Test lud Chromium 156
https://revoked-isrgrootx1.letsencrypt.org/mit HTTP 200. - Apple: eine aggregierte Liste. Apple sammelt Widerrufsdaten von den CAs und verteilt sie an die Geräte. Im Dezember 2025 erklärte Apples Developer Technical Support, dass Zertifikate „nur auf Apples ‚schwarzer Liste‘ landen“, wenn sie aus Gründen wie einer Kompromittierung des Schlüssels widerrufen wurden, und dass es für Zertifikate von Let's Encrypt keine nachgelagerte OCSP-Prüfung gibt. Eine Website mit einem aus gewöhnlichem Grund widerrufenen Zertifikat lädt in Safari daher womöglich weiterhin.
Was das in der Praxis bedeutet: Für Browser ist der Widerruf eines Zertifikats kein schneller Notausschalter. Firefox erfährt von Widerrufen über seine CRLite-Updates im 12-Stunden-Takt. Chrome und Safari blockieren ein aus gewöhnlichem Grund widerrufenes Zertifikat womöglich nie. Wie lange ein abgeflossener Schlüssel nutzbar bleibt, begrenzt letztlich die Restlaufzeit des Zertifikats – einer der Gründe für den Übergang zu 47-Tage-Zertifikaten.
Was Bibliotheken und Tools standardmäßig prüfen
Jeder der folgenden Clients hat sich am 20. Oktober 2026 mit https://revoked-isrgrootx1.letsencrypt.org/ (widerrufen am 7. Oktober 2026) und https://valid-isrgrootx1.letsencrypt.org/ verbunden. Das widerrufene Zertifikat hat eine CRL-URL und keine OCSP-URL.
| Client | Standardergebnis bei der widerrufenen Website | Widerrufsprüfung, die funktioniert hat |
|---|---|---|
| Java 25 (JSSE) | HTTP 200 | -Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true |
.NET 10 HttpClient | HTTP 200 | SslOptions.CertificateRevocationCheckMode = X509RevocationMode.Online |
Go 1.27 crypto/tls | 200 OK | Eigene Prüfung in tls.Config.VerifyConnection |
Python 3.13 ssl | Handshake erfolgreich | VERIFY_CRL_CHECK_LEAF und dazu die in den Kontext geladene CRL |
Node.js 24 tls | Handshake erfolgreich | Option crl mit einer CRL pro Array-Element, für jede Ebene der Kette |
| curl 8.14 (OpenSSL) | HTTP 200 | --crlfile mit CRLs für jede Ebene der Kette |
| Chromium 156 | HTTP 200 | Keine (nur CRLSet) |
Die Details zu den einzelnen Clients folgen.
Java
JSSE prüft den Widerruf nicht, solange com.sun.net.ssl.checkRevocation nicht true ist. Selbst dann nutzt es nur OCSP, sofern nicht auch CRL Distribution Points aktiviert sind. Deshalb schlägt die Prüfung eines Zertifikats von Let's Encrypt als unbekannt fehl und nicht als widerrufen:
--- 1. default
HTTP 200
--- 2. -Dcom.sun.net.ssl.checkRevocation=true
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path validation failed: java.security.cert.CertPathValidatorException: Could not determine revocation status
--- 3. checkRevocation + -Dcom.sun.security.enableCRLDP=true (revoked site)
javax.net.ssl.SSLHandshakeException: (certificate_revoked) PKIX path validation failed: java.security.cert.CertPathValidatorException: Certificate has been revoked, reason: UNSPECIFIED, revocation date: Wed Oct 07 18:37:19 UTC 2026, authority: CN=YR1, O=Let's Encrypt, C=US, extension OIDs: []
--- 4. same, valid site
HTTP 200
Ist die Prüfung aktiviert, bricht der PKIX-Validator ab (Hard-Fail), sobald er keinen Status ermitteln kann. Das ist sicher, aber ein Ausfall des CRL-Servers wird dann zu deinem Ausfall. Mit java.security.cert.PKIXRevocationChecker legst du pro Verbindung die Reihenfolge von OCSP und CRL sowie Soft-Fail-Optionen fest.
.NET
HttpClient mit SocketsHttpHandler prüft den Widerruf standardmäßig nicht. Setzt du den Modus auf Online, ruft .NET die CRL oder die OCSP-Antwort selbst ab. Unter Linux geschieht das über eine eigene Implementierung auf Basis von OpenSSL:
var handler = new SocketsHttpHandler();
handler.SslOptions.CertificateRevocationCheckMode = X509RevocationMode.Online;
using var client = new HttpClient(handler);
https://revoked-isrgrootx1.letsencrypt.org/ revocation=default -> 200
https://revoked-isrgrootx1.letsencrypt.org/ revocation=Online -> The remote certificate is invalid because of errors in the certificate chain: Revoked
https://valid-isrgrootx1.letsencrypt.org/ revocation=default -> 200
https://valid-isrgrootx1.letsencrypt.org/ revocation=Online -> 200
Go
crypto/tls prüft die Kette und den Namen, aber nie den Widerruf. Wenn du ihn brauchst, prüfe die CRL nach der Kettenprüfung selbst. x509.ParseRevocationList und CheckSignatureFrom übernehmen die schwierigen Teile:
func checkCRL(cs tls.ConnectionState) error {
chain := cs.VerifiedChains[0]
leaf, issuer := chain[0], chain[1]
if len(leaf.CRLDistributionPoints) == 0 {
return errors.New("certificate has no CRL distribution point")
}
resp, err := http.Get(leaf.CRLDistributionPoints[0])
if err != nil {
return err
}
defer resp.Body.Close()
der, err := io.ReadAll(io.LimitReader(resp.Body, 64<<20))
if err != nil {
return err
}
crl, err := x509.ParseRevocationList(der)
if err != nil {
return err
}
if err := crl.CheckSignatureFrom(issuer); err != nil {
return fmt.Errorf("CRL signature: %w", err)
}
if time.Now().After(crl.NextUpdate) {
return errors.New("CRL is past its nextUpdate")
}
for _, entry := range crl.RevokedCertificateEntries {
if entry.SerialNumber.Cmp(leaf.SerialNumber) == 0 {
return fmt.Errorf("certificate %X revoked at %s", leaf.SerialNumber, entry.RevocationTime.UTC())
}
}
return nil
}
// client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{VerifyConnection: checkCRL}}}
https://revoked-isrgrootx1.letsencrypt.org/ -> 200 OK
https://revoked-isrgrootx1.letsencrypt.org/ -> Get "https://revoked-isrgrootx1.letsencrypt.org/": certificate 67E51010C975E496C8EB800F3EF1BAB3F2B revoked at 2026-10-07 18:37:19 +0000 UTC
https://valid-isrgrootx1.letsencrypt.org/ -> 200 OK
https://valid-isrgrootx1.letsencrypt.org/ -> 200 OK
Das Beispiel prüft nur das Leaf-Zertifikat und lädt die CRL bei jedem Handshake. Im Produktivbetrieb speicherst du jede CRL bis zu ihrem NextUpdate im Cache und legst fest, was passiert, wenn der CRL-Server ausfällt.
Python, Node.js und curl
Alle drei nutzen die CRL-Prüfung von OpenSSL und erwarten, dass du die CRLs bereitstellst. Keiner von ihnen lädt sie selbst herunter:
python 3.13.5 default: handshake ok, TLSv1.3
python 3.13.5 VERIFY_CRL_CHECK_LEAF: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate revoked (_ssl.c:1029)
node v24.22.0 default: handshake ok
node v24.22.0 crl: UNABLE_TO_GET_CRL unable to get certificate CRL
ssl.VERIFY_CRL_CHECK_LEAF in Python prüft nur das Leaf-Zertifikat, daher genügt es, dessen CRL mit ctx.load_verify_locations(cafile=...) zu laden. Node.js und curl prüfen jedes Zertifikat der Kette, sodass eine CRL allein für das Leaf-Zertifikat sogar bei einer gültigen Website zu UNABLE_TO_GET_CRL (Node) oder unable to get certificate CRL (curl) führt. Gib ihnen die CRL jedes Zertifikats der Kette. Für Node.js 24 übergibst du sie als Array mit einem PEM-String pro CRL. Ein einzelner Buffer mit drei aneinandergehängten CRLs schlug in unserem Test trotzdem mit UNABLE_TO_GET_CRL fehl:
revoked-isrgrootx1.letsencrypt.org (one buffer): UNABLE_TO_GET_CRL unable to get certificate CRL
revoked-isrgrootx1.letsencrypt.org (array): CERT_REVOKED certificate revoked
valid-isrgrootx1.letsencrypt.org (one buffer): UNABLE_TO_GET_CRL unable to get certificate CRL
valid-isrgrootx1.letsencrypt.org (array): handshake ok
Die beiden Optionen von curl verhalten sich unterschiedlich. --cert-status verlangt eine per Stapling mitgelieferte OCSP-Antwort und scheitert daher an jedem Server, der kein Stapling betreibt – also auch an allen Websites mit Zertifikaten von Let's Encrypt. --crlfile funktioniert mit einer Datei, die die CRLs für die gesamte Kette enthält:
$ curl -s -o /dev/null -w "HTTP %{http_code}\n" https://revoked-isrgrootx1.letsencrypt.org/
HTTP 200
$ curl -sS -o /dev/null --cert-status https://revoked-isrgrootx1.letsencrypt.org/
curl: (91) No OCSP response received
$ curl -sS -o /dev/null --crlfile revoked-chain-crls.pem https://revoked-isrgrootx1.letsencrypt.org/
curl: (60) SSL certificate problem: certificate revoked
$ curl -s -o /dev/null -w "HTTP %{http_code}\n" --crlfile valid-chain-crls.pem https://valid-isrgrootx1.letsencrypt.org/
HTTP 200
$ curl -sS -o /dev/null --crlfile leaf-crl-only.pem https://valid-isrgrootx1.letsencrypt.org/
curl: (60) SSL certificate problem: unable to get certificate CRL
Dieses Skript sammelt die CRL jedes Zertifikats, das ein Server sendet:
#!/bin/bash
host="$1"; out="$2"
dir=$(mktemp -d)
openssl s_client -connect "$host:443" -servername "$host" -showcerts </dev/null 2>/dev/null \
| awk -v d="$dir" '/BEGIN CERTIFICATE/{n++} n>0{print > (d "/cert" n ".pem")}'
: > "$out"
for cert in "$dir"/cert*.pem; do
url=$(openssl x509 -in "$cert" -noout -ext crlDistributionPoints 2>/dev/null | grep -o 'http[^ ]*' | head -1)
[ -n "$url" ] && curl -s "$url" | openssl crl -inform DER >> "$out"
done
rm -r "$dir"
echo "$host: $(grep -c 'BEGIN X509 CRL' "$out") CRLs"
Ein Zertifikat von Hand prüfen
Mit OpenSSL siehst du, wo der Status eines Zertifikats hinterlegt ist, und kannst ihn direkt prüfen. Sieh dir zuerst die Erweiterungen an:
$ openssl x509 -in leaf.pem -noout -ext crlDistributionPoints,authorityInfoAccess
Authority Information Access:
CA Issuers - URI:http://yr1.i.lencr.org/
X509v3 CRL Distribution Points:
Full Name:
URI:http://yr1.c.lencr.org/44.crl
Lade dann die CRL herunter und prüfe das Zertifikat damit. -untrusted liefert die Zwischenzertifikate, die der Server gesendet hat:
$ curl -s -o crl.der http://yr1.c.lencr.org/44.crl
$ openssl crl -inform DER -in crl.der -out crl.pem
$ openssl verify -crl_check -CRLfile crl.pem -untrusted chain.pem leaf.pem
CN=revoked-isrgrootx1.letsencrypt.org
error 23 at 0 depth lookup: certificate revoked
error leaf.pem: verification failed
$ openssl crl -in crl.pem -noout -text | grep -A1 "Serial Number: 067E51010C975E496C8EB800F3EF1BAB3F2B"
Serial Number: 067E51010C975E496C8EB800F3EF1BAB3F2B
Revocation Date: Oct 7 18:37:19 2026 GMT
Bei einer CA, die noch OCSP betreibt, frag den Responder direkt oder sieh dir die per Stapling mitgelieferte Antwort an:
$ openssl ocsp -issuer issuer.pem -cert leaf.pem -url http://ocsp.digicert.com -resp_text -noverify
OCSP Response Status: successful (0x0)
Cert Status: good
This Update: Oct 19 22:09:00 2026 GMT
Next Update: Oct 26 21:09:00 2026 GMT
leaf.pem: good
This Update: Oct 19 22:09:00 2026 GMT
Next Update: Oct 26 21:09:00 2026 GMT
$ openssl s_client -connect www.digicert.com:443 -servername www.digicert.com -status </dev/null
OCSP Response Status: successful (0x0)
Cert Status: good
-noverify überspringt die Prüfung der Signatur der Antwort. Für einen schnellen Blick ist das in Ordnung, als Grundlage einer Entscheidung nicht. Der OCSP-Decoder parst Anfragen und Antworten, prüft Signatur und Berechtigung des Responders und kann einen Responder für dich abfragen.
Was du in deinem Dienst tun solltest
- Entscheide, ob du Widerrufsprüfungen überhaupt brauchst. Für ausgehende Aufrufe öffentlicher APIs genügt meist das Browsermodell (kurze Laufzeiten, CT, Reaktion der CAs auf Vorfälle). Bei Verbindungen mit hohem Schutzbedarf, etwa Zahlungs-Callbacks, mTLS zwischen Organisationen oder Code- und Dokumentensignaturen, prüfe den Widerruf explizit.
- Unterstütze zuerst CRLs. Öffentliche CAs müssen sie veröffentlichen, und einige betreiben kein OCSP mehr. Ein Client, der nur OCSP beherrscht, kann bei Zertifikaten von Let's Encrypt und Google Trust Services keine Entscheidung treffen.
- Speichere CRLs zwischen und aktualisiere sie nach ihrem Zeitplan. Rufe jede CRL einmal ab, behalte sie bis
nextUpdateund aktualisiere sie im Hintergrund, damit ein TLS-Handshake nie auf einen Download wartet. - Wähle Fail-open oder Fail-closed bewusst. Fail-closed macht ein CDN-Problem einer CA zu deinem Ausfall. Fail-open macht die Prüfung für jeden optional, der den Download der CRL blockieren kann. Protokolliere beide Fälle.
- Gib Abhängigkeiten von Must-Staple und OCSP-Stapling auf. Let's Encrypt lehnt Must-Staple-Anfragen ab, und Stapling hilft nur bei CAs, die noch OCSP betreiben.
- Eine private PKI gestaltest du selbst. Veröffentliche CRLs mit kurzem
nextUpdateoder stelle kurzlebige Zertifikate aus und verzichte auf den Widerruf, so wie es die BR inzwischen für Zertifikate mit höchstens 7 Tagen Laufzeit erlauben.
Quellen
- CA/Browser Forum, Baseline Requirements v2.3.1: §4.9.1.1 (Fristen für den Widerruf), §4.9.7 (Aktualisierungsintervall der CRLs), §4.9.9 (OCSP), §7.1.2.11.2 (CRL Distribution Points), §7.2 (CRL-Profil), Definition von „Short-lived Subscriber Certificate“. Ballot SC-063.
- RFC 5280 (CRLs) und RFC 6960 (OCSP).
- Let's Encrypt, Ende der OCSP-Unterstützung im Jahr 2025.
- Mozilla, CRLite: schnelle, private und umfassende Widerrufsprüfung von Zertifikaten in Firefox (August 2025), und die Release Notes von Firefox für Android 145.
- Chromium, CRLSets.
- Apple Developer Forums, Thread 808875 (Dezember 2025).
Die Tests liefen am 20. Oktober 2026 in node:24.22.0-trixie (OpenSSL 3.5.7, curl 8.14.1, Node.js 24.22.0, Python 3.13.5), eclipse-temurin:25.0.4.1_1-jdk-noble, golang:1.27.3-trixie, mcr.microsoft.com/dotnet/sdk:10.0.402-noble und mcr.microsoft.com/playwright:v1.64.1-noble (Chromium 156).