# OpenSSL OCSP and CRL commands: check certificate revocation

> Check certificate revocation with OpenSSL 3.x: find OCSP and CRL URLs, query responders, read stapled responses, verify with -crl_check.

Source: https://ssl.sh/cookbook/ocsp-crl/
Last updated: 2026-11-10

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](https://ssl.sh/learn/ocsp-and-crl/).

## Find the OCSP URL and CRL distribution point

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

```bash
openssl x509 -in cert.pem -noout -ocsp_uri
openssl x509 -in cert.pem -noout -ext authorityInfoAccess,crlDistributionPoints
```

```text
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.

```bash
openssl ocsp -issuer intermediate.pem -cert cert.pem -url http://ocsp.example.net
```

```text
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](https://ssl.sh/cookbook/verify-chains/)).

## Read the full OCSP response

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

```bash
openssl ocsp -issuer intermediate.pem -cert cert.pem -url http://ocsp.example.net -resp_text -no_nonce
```

```text
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.

```bash
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'
```

```text
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.

```bash
openssl x509 -in cert.pem -noout -ext tlsfeature
```

```text
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](https://ssl.sh/glossary/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.

```bash
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
```

```text
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`.

```bash
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
```

```text
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.

```bash
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"
```

```text
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.

```bash
openssl crl -inform DER -in ca.crl -CAfile intermediate.pem -noout
```

```text
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.

```bash
openssl verify -crl_check -CRLfile ca.crl -untrusted chain.pem cert.pem
openssl verify -crl_check -crl_download -untrusted chain.pem cert.pem
```

```text
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](https://ssl.sh/errors/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.

```bash
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
```

```text
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](https://ssl.sh/cookbook/private-ca/).
