Secrets, PKI & CertificatesIX · Certificate Lifecycle and RevocationLifecycle
Revocation inside a private PKI
What you'll learn
- Identify which verifiers check revocation by default and which never do
- Configure a fail-closed check against a local artefact rather than a network fetch
- Explain how a delta CRL combines with its base and when it earns its complexity
- Judge whether certificateHold is reversible in a private PKI for your clients
Prerequisites
Verified against OpenSSL 3.5.x teaching target; 3.0+ minimum · OpenSSH 10.x teaching target; 8.2+ minimum for certificate workflows · OpenBao 2.6.x · Smallstep step-ca 0.30.x · Certbot / Pebble Certbot current release; Pebble 2.10.x ACME test server · Kubernetes (cross-course target) 1.36.x · PostgreSQL 17.x · 2026-08-26
Inside your own certificate authority the calculation from the previous lesson changes completely. You control the issuer, the publication schedule and, crucially, every client that validates. Revocation can be made to work here. It just does not work by accident, because every widely used verification library ships with revocation checking switched off, and enabling it turns a local question into either a file dependency or a network dependency depending on how you do it.
Nothing checks a CRL unless you switch it on
Start with the defaults, because they are more permissive than most operators assume. The OpenSSL verification parameters documentation states that unless noted otherwise each flag is off by default, and the revocation flags are not among the exceptions.
Four of them interact in ways worth memorising. X509_V_FLAG_CRL_CHECK
enables checking for the end-entity certificate only.
X509_V_FLAG_CRL_CHECK_ALL extends the check to the whole chain, and
is documented as otherwise ignored if the first flag is not also set,
so setting only the second achieves nothing. X509_V_FLAG_USE_DELTAS
governs delta CRLs, and if it is not set, deltas are ignored
entirely. X509_V_FLAG_EXTENDED_CRL_SUPPORT covers indirect CRLs and
partitioned lists, and those features are disabled by default too.
On the command line the same flags appear as opt-in options.
CA=/etc/pki/root.crt
INTERMEDIATE=/etc/pki/issuing-ca.crt
CRL=/etc/pki/issuing-ca.crl
LEAF=/etc/pki/app.lab.example.crt
# Without -crl_check this succeeds even for a revoked certificate.
openssl verify -CAfile "$CA" -untrusted "$INTERMEDIATE" "$LEAF"
# With it, a valid CRL for the leaf's issuer must be found or
# verification fails.
openssl verify -crl_check -CAfile "$CA" -untrusted "$INTERMEDIATE" \
-CRLfile "$CRL" "$LEAF"
The second invocation has the property the first lacks. The documentation is explicit that the check works by attempting to look up a valid CRL and that an error occurs if one cannot be found. That is fail-closed behaviour, and it is exactly what you want, provided the artefact it needs is genuinely always present.
Note which flag is used. -crl_check covers the leaf, and one CRL
from the issuing CA satisfies it. -crl_check_all covers every
certificate in the chain, so it also demands a CRL that covers the
intermediate — one the root publishes. Passing -crl_check_all with
only the issuing CA’s CRL fails on a perfectly valid certificate with
unable to get certificate CRL at depth 1, which reads exactly like a
revocation to anyone not watching the depth. If you want whole-chain
checking, supply a bundle containing every CA’s CRL.
Other stacks are less forgiving. The Go standard library states plainly in the documentation for certificate verification that the function performs no revocation checking at all, and ships CRL types for producing lists rather than consuming them. Java disables revocation when the trust manager is initialised from a key store, and needs both revocation checking and CRL distribution point following to be turned on before anything happens. And the Kubernetes API server documentation is blunt about client certificate authentication: the platform does not support certificate revocation, and any certificate that is issued remains valid until it expires.
Where enforcement actually works
Two patterns produce dependable revocation, and they share a shape: the check consults a local artefact, and the absence of that artefact is a hard failure rather than a shrug.
The first is a reverse proxy validating client certificates against a CRL file on disk. The relevant defaults are all permissive, so every line has to be written deliberately.
ssl_verify_client on;
ssl_client_certificate /etc/pki/issuing-ca.crt;
ssl_crl /etc/pki/issuing-ca.crl;
Client verification is off by default, the CRL directive has no
default at all, and the OCSP validation of client certificates is
also off by default. Because the list is a file, there is no
resolver, no timeout and no third party on the connection path. The
cost moves to distribution: that file must be refreshed before its
own nextUpdate passes, on every node.
The second pattern is the one to copy, and it comes from SSH rather than X.509. Revocation there is a local list named in the daemon configuration:
TrustedUserCAKeys /etc/ssh/user_ca.pub
RevokedKeys /etc/ssh/revoked.krl
PubkeyAuthentication yes
The documentation adds the sentence that makes it trustworthy: if that file is not readable, then public key authentication will be refused for all users. There is no soft-fail path, no network fetch and no ambiguity about a stale artefact. It is the structural opposite of an online status query, and it is why SSH revocation behaves predictably where TLS revocation frequently does not.
flowchart LR
A["Verifier"] --> B{"Revocation check\nexplicitly enabled?"}
B -- "no" --> C["Certificate accepted\nuntil it expires"]
B -- "yes" --> D{"Artefact local\nor fetched?"}
D -- "local file" --> E["Fail-closed\nno network dependency"]
D -- "network fetch" --> F["Inherits soft-fail\nand a new dependency"]
The diagram compresses the whole design decision. A verifier that was never configured to check accepts a revoked certificate for its full remaining lifetime. A verifier that checks against a file gives you enforcement at the cost of a distribution obligation. A verifier that checks over the network gives you enforcement at the cost of a runtime dependency on your own control plane, plus the same soft-fail question the public Web PKI already answered against you.
Delta CRLs, where they genuinely belong
A complete CRL grows with every revocation and every client fetches all of it. Delta CRLs address that by publishing only what changed since a named base list. In the public Web PKI they are effectively excluded, because the Baseline Requirements permit only the authority key identifier, the CRL number and the issuing distribution point as CRL extensions and mark everything else as not recommended. Inside a private PKI they are a legitimate tool.
The rules are strict and several of them are easy to get wrong. The delta CRL indicator is a critical CRL extension, so a client that does not understand it must reject the list rather than misread it as a complete one. The base list it references must itself be published as a complete CRL. A delta must cover the same set of reasons and the same set of certificates as its base. The same private key must sign both. The freshest CRL extension, which points at where deltas are published, must be non-critical, must not appear inside a delta itself, and carries only a distribution point with its reasons and issuer fields omitted.
certificateHold, the suspension trap of a private PKI
Suspension is the other capability that exists only outside the public Web PKI. There, the requirements state that the repository must not include entries indicating that a certificate is suspended, and the sections covering suspension are simply marked as not applicable. In a private PKI you may use the hold reason, and there are legitimate cases for it: a laptop reported missing that may yet be found, a contractor on suspension pending an investigation, a service quarantined during an incident.
The practical advice is to prefer reissuance over release. If a suspended identity is cleared, issuing a fresh certificate is a deterministic operation with no dependency on client behaviour, while lifting a hold is a bet on client configuration you may not control.
Operating a CRL you can actually depend on
Running the publication side is an availability responsibility, and the failure modes are specific to the design.
Rebuilding a complete list is expensive at scale. A widely deployed private PKI engine documents that it must read every revoked certificate into memory in order to rebuild the list, describes that as an expensive operation, and warns that several hundred thousand stored unexpired certificates can negatively affect even a large cluster. The same guidance draws the conclusion this part has been building towards: a shorter certificate lifetime decreases the likelihood of needing to revoke at all, and reduces the impact when you do.
Built-in status responders in private PKI software carry their own limitations, and they are documented rather than hidden. One mainstream implementation answers for only a single serial per request, supports none of the protocol extensions defined in the specification, which means no nonce and therefore no replay protection, and does not support responders for authorities using certain key types. Those are acceptable constraints for an internal deployment as long as you know about them before you design a control around the responder.
Finally, monitor the publication itself. Alert when the CRL number
stops advancing, when the promised nextUpdate passes without a new
list, when the signature fails to verify against the expected issuer,
and when the distribution endpoint serves a list whose scope does not
match what the certificates point at. In a private PKI these are your
outages, and a stale list eventually becomes either a security hole
or, with fail-closed verification, a complete authentication failure.
Production discipline
- Assume checking is off until you have proved it is on. Test by attempting a connection with a genuinely revoked certificate, not by reading configuration.
- Prefer a local artefact to a network fetch. A file distributed by configuration management fails in ways you already understand and monitor.
- Enable the whole-chain flag alongside the leaf flag. The chain option is ignored on its own, so a partially configured verifier checks less than its author believes.
- Give the CRL an owner and a freshness alert. The publication schedule is a promise the certificates make on your behalf.
- Reissue rather than release. Lifting a hold depends on client behaviour you probably cannot audit; issuing a new certificate does not.
Cross-course references
- Kubernetes for Production Sysadmins - Part LVII (Authentication) covers client certificate authentication in a platform that has no revocation mechanism, where short lifetimes and binding removal are the only levers.
- Linux for Production Sysadmins - Part XXVI (SSH) covers the SSH trust model whose revoked-keys handling is the fail-closed pattern this lesson recommends copying.
- Observability for Production Sysadmins - Part XVIII (AlertingRules) covers writing the freshness alerts that keep a private CRL from silently going stale.
Quiz
Knowledge check · 4 questions
Q1. A verifier is configured with the flag that extends CRL checking to the whole chain, but not the flag that enables CRL checking for the end-entity certificate. What happens?
Q2. The SSH daemon refuses public key authentication for all users when its configured revoked-keys file cannot be read.
Q3. Which reason code removes an entry from a revocation list, where may it appear, and what does that imply for releasing a certificate from hold?
Q4. Decide whether the control is real and what you would change.
An internal mutual TLS estate issues client certificates from a private authority with a two-year validity. A security review records that revoked client certificates are rejected because the authority publishes a CRL every six hours. The proxies terminating mutual TLS were configured a year ago and their configuration sets client verification on and names the issuing CA bundle. No CRL file appears in the proxy configuration.
Passing score: 75%. Answers are checked in this browser.