Verification and testing

OpenSSL OCSP and CRL commands: check certificate revocation

By · Last verified Tested macOS · OpenSSL 3.6.1

OpenSSL 3.x can find the revocation URLs in a certificate, query an OCSP responder or CRL, and check a chain against them. OCSP is a per-certificate online query; a CRL is a signed list of revoked serial numbers. Let's Encrypt ended OCSP in 2025, so its certificates are checked through CRLs only. Background is in OCSP and CRL explained.

Find the OCSP URL and CRL distribution point

The OCSP URL lives in Authority Information Access; the CRL URL in CRL Distribution Points.

openssl x509 -in cert.pem -noout -ocsp_uri
openssl x509 -in cert.pem -noout -ext authorityInfoAccess,crlDistributionPoints
Authority Information Access: 
    CA Issuers - URI:http://ye2.i.lencr.org/
X509v3 CRL Distribution Points: 
    Full Name:
      URI:http://ye2.c.lencr.org/51.crl

This is a Let's Encrypt certificate checked in November 2026: -ocsp_uri printed nothing and the AIA extension only has a CA Issuers URL. The CRL URL ends in a shard number because Let's Encrypt splits its CRLs into many smaller files. A certificate from a CA that still runs OCSP prints a line such as http://ocsp.example.net for -ocsp_uri.

Query an OCSP responder

openssl ocsp needs the issuer certificate, the certificate to check and the responder URL.

openssl ocsp -issuer intermediate.pem -cert cert.pem -url http://ocsp.example.net
WARNING: no nonce in response
Response verify OK
cert.pem: good
    This Update: Nov  8 22:09:00 2026 GMT
    Next Update: Nov 15 21:09:00 2026 GMT

The status is good, revoked or unknown. Response verify OK means the response signature chains to a trusted root; if your default store lacks that root, add -CAfile root.pem or, for a quick look only, -noverify. Most public responders serve pre-signed responses and ignore the nonce, hence the warning; add -no_nonce to suppress it. Get intermediate.pem from the server with -showcerts or from the CA Issuers URL (verifying chains).

Read the full OCSP response

-resp_text prints the responder ID, the certificate ID and the validity window of the response.

openssl ocsp -issuer intermediate.pem -cert cert.pem -url http://ocsp.example.net -resp_text -no_nonce
OCSP Response Data:
    OCSP Response Status: successful (0x0)
    Response Type: Basic OCSP Response
    Version: 1 (0x0)
    Responder Id: 6A4E50BF98689D5B7B2075D45901794866923206
    Produced At: Nov  8 22:25:24 2026 GMT
    Responses:
    Certificate ID:
      Hash Algorithm: sha1
      Issuer Name Hash: D613075FB6DEA11BDF0182D397E1D37C6E925509
      Issuer Key Hash: 6A4E50BF98689D5B7B2075D45901794866923206
      Serial Number: 0F07FCB7C21FB7667C1BFD9496F28B4C
    Cert Status: good
    This Update: Nov  8 22:09:00 2026 GMT
    Next Update: Nov 15 21:09:00 2026 GMT

The certificate ID is built from hashes of the issuer's name and public key plus the serial number, which is why the issuer is mandatory. SHA-1 here is only an identifier, not a signature. A response is valid between This Update and Next Update; a server stapling an expired response causes errors in strict clients.

Check the OCSP response a server staples

s_client -status requests stapling and prints the stapled response if there is one.

openssl s_client -connect www.example.com:443 -servername www.example.com -status < /dev/null 2>/dev/null \
  | sed -n '/OCSP response/,/Next Update/p'
OCSP responses: number of responses: 1
======================================
OCSP Response Data:
    OCSP Response Status: successful (0x0)
    Response Type: Basic OCSP Response
    ...
    Cert Status: good
    This Update: Nov  9 04:50:45 2026 GMT
    Next Update: Nov 16 04:50:45 2026 GMT

OCSP responses: no responses sent means nothing was stapled. That is expected when the certificate has no OCSP URL. With an OCSP URL present, Nginx fetches the response lazily, so the first request after a reload often shows no staple; run the command twice. OpenSSL versions before 3.x print the singular form OCSP response: no response sent.

Check whether a certificate requires stapling (Must-Staple)

The TLS Feature extension with status_request means the certificate demands a stapled OCSP response.

openssl x509 -in cert.pem -noout -ext tlsfeature
TLS Feature: 
    status_request

No output means the extension is absent, which is normal. Clients that enforce Must-Staple (Firefox has done so for years) reject the connection when no staple arrives. Let's Encrypt stopped issuing Must-Staple certificates on May 7, 2025, together with dropping OCSP URLs, so remove any --must-staple flag from old ACME client configurations. See Must-Staple.

Save an OCSP response and staple it from a file

-respout writes the DER response to a file that a server can staple, which is useful when the server cannot reach the responder.

openssl ocsp -issuer intermediate.pem -cert cert.pem -url http://ocsp.example.net \
  -no_nonce -respout cert.ocsp
openssl s_server -accept 8443 -cert cert.pem -key key.pem -cert_chain intermediate.pem \
  -status_file cert.ocsp -www
Response verify OK
cert.pem: good
    This Update: Nov  9 18:19:28 2026 GMT

Test with openssl s_client -connect 127.0.0.1:8443 -status. Nginx's ssl_stapling_file and HAProxy's .ocsp files take the same DER format. Refresh the file well before Next Update, typically from cron.

Download and read a CRL

CRLs are usually served as DER over plain HTTP; read them with openssl crl -inform DER.

curl -s -o ca.crl http://ye2.c.lencr.org/51.crl
openssl crl -inform DER -in ca.crl -noout -issuer -lastupdate -nextupdate -crlnumber
openssl crl -inform DER -in ca.crl -noout -text | head -25
issuer=C=US, O=Let's Encrypt, CN=YE2
lastUpdate=Nov  9 17:52:09 2026 GMT
nextUpdate=Nov 18 17:52:08 2026 GMT
crlNumber=0x18E671E4DCCF25A0
Certificate Revocation List (CRL):
    ...
        X509v3 Issuing Distribution Point: critical
            Full Name:
              URI:http://ye2.c.lencr.org/51.crl
            Only User Certificates
Revoked Certificates:
    Serial Number: 05E7C117F64B64BC83F3691B78C8F454F1B2
        Revocation Date: Sep 24 17:49:02 2026 GMT
    Serial Number: 06D1E637E94367185DAF628054DAD4CED532
        Revocation Date: Aug 17 16:24:19 2026 GMT
        CRL entry extensions:
            X509v3 CRL Reason Code: 
                Key Compromise

This shard was about 100 KB with 2,345 entries. The Issuing Distribution Point marks it as a partitioned CRL that only covers end-entity certificates. If openssl crl fails with a decoding error, the file is probably PEM already: drop -inform DER.

Look up one certificate on a CRL

Take the serial number from the certificate and search for it in the decoded CRL.

SERIAL=$(openssl x509 -in cert.pem -noout -serial | cut -d= -f2)
echo "$SERIAL"
openssl crl -inform DER -in ca.crl -noout -text | grep -c "$SERIAL"
05688C92AD512A3678DB6355831A0BFBBD32
0

0 means the serial is not on this CRL. Both commands print uppercase hex without colons, so a plain grep works. Make sure you downloaded the CRL named in this certificate's CRL Distribution Points; with sharded CRLs, another shard tells you nothing.

Verify the signature of a CRL

openssl crl -CAfile checks that the CRL was signed by the given issuer.

openssl crl -inform DER -in ca.crl -CAfile intermediate.pem -noout
verify OK

A failure (verify failure) means the CRL was signed by another key, for example a different intermediate generation. Always verify a CRL you fetched over HTTP before trusting it: integrity comes from the signature, not the transport.

Verify a chain with CRL checking

openssl verify -crl_check checks the leaf against a CRL; -crl_download fetches the CRL from the certificate itself.

openssl verify -crl_check -CRLfile ca.crl -untrusted chain.pem cert.pem
openssl verify -crl_check -crl_download -untrusted chain.pem cert.pem
cert.pem: OK
cert.pem: OK

A revoked certificate fails with error 23 at 0 depth lookup: certificate revoked. -crl_check_all also requires CRLs for the intermediates; without them you get error 3 at 1 depth lookup: unable to get certificate CRL. OpenSSL 3.x reads both PEM and DER CRLs in -CRLfile. Revoked certificates show up in browsers as NET::ERR_CERT_REVOKED.

Run a test OCSP responder for a private CA

openssl ocsp -index serves status from an openssl ca database, which is enough for lab work and stapling tests.

openssl ocsp -index ica/index.txt -port 8888 -rsigner ica/ica.crt -rkey ica/ica.key \
  -CA ica/ica.crt -nrequest 1 &
openssl ocsp -issuer ica/ica.crt -cert old.crt -url http://127.0.0.1:8888 -CAfile root.crt
Response verify OK
old.crt: revoked
    This Update: Nov  9 18:19:28 2026 GMT
    Reason: keyCompromise
    Revocation Time: Nov  9 18:18:25 2026 GMT

-port takes a bare port number (127.0.0.1:8888 is rejected), and -nrequest 1 exits after one request. The responder is single-threaded and not meant for production. Building the CA and its index.txt is covered in running a private CA.

FAQ

Why does openssl x509 -ocsp_uri print nothing?

The certificate has no OCSP URL in its Authority Information Access extension. Since the CA/Browser Forum made OCSP optional for publicly trusted CAs, several CAs have dropped it; Let's Encrypt stopped including OCSP URLs on May 7, 2025. Check the CRL Distribution Points extension instead.

Should I still enable OCSP stapling in Nginx?

It does no harm for certificates that still have an OCSP URL, and it saves clients a lookup. For certificates without one, such as current Let's Encrypt certificates, Nginx has nothing to staple and logs a warning; you can turn ssl_stapling off for those hosts.

Do browsers download CRLs for every site?

No. Chrome uses CRLSets and Firefox uses CRLite, both of which are compact revocation summaries built by the browser vendor from CA CRLs and pushed to the browser. Direct CRL and OCSP checks are mostly done by non-browser software. See OCSP and CRL explained.

Sources

  1. OpenSSL manual: openssl-ocsp
  2. OpenSSL manual: openssl-crl
  3. RFC 6960: X.509 Internet PKI Online Certificate Status Protocol (OCSP)
  4. RFC 5280: Certificate and CRL Profile
  5. Let's Encrypt: Ending OCSP Support in 2025