You revoked the certificate, generated a fresh list, and the old profile still brings up a tunnel. Nothing is broken in the revocation itself: a CRL is a signed statement that a serial number is no longer valid, and it changes access only when the component terminating TLS loads it, for the right issuer, before it makes the accept decision. That last part is where most of these cases live, and it is why revocation and denial are separate facts.

Work through the five checks below in order, and stop at the first one that does not hold, because each later check assumes the earlier ones are true.

Check which component makes the accept decision

Name the process that validates the client certificate, because only that process can enforce a list. On an OpenVPN server it is the daemon itself, configured with crl-verify. On ocserv it is the crl line in ocserv.conf. On strongSwan the inputs are the loaded CRLs together with strictcrlpolicy. A TLS-terminating proxy or appliance in front of the VPN can be the real verifier too, and if it is, the daemon behind it never sees the client certificate.

WireGuard belongs in a different category. It has no certificates and no revocation list; a peer is admitted by its public key in the configuration, so ending access means removing that key. If you are hunting for a CRL on a WireGuard-only deployment, the mechanism is not there.

Watch for the client-side trap as well. A client that refuses to connect because it dislikes the server certificate is enforcing the server's chain, not revoking its own credential. A user certificate is only revoked where that user certificate is validated, which is almost always the server side.

Check the issuer the list was signed for

A CRL is scoped to one issuing CA. With a root and an intermediate, leaf certificates come from the intermediate, so the list that matters is the intermediate's CRL; the root's list says nothing about those leaves. A verifier pointed at the wrong file parses it happily, finds no matching entry, and accepts everything.

Confirm the pairing before touching the daemon. The issuer on the certificate must match the subject of the list:

openssl x509 -in client.crt -noout -issuer -serial
openssl crl  -in intermediate.crl -noout -issuer -lastupdate -nextupdate

When both come from the same CA, the serial printed for the certificate is the value that must appear in the list. If they disagree, you are holding the wrong CRL or the wrong copy of it.

Validity belongs to the same check. A nextUpdate in the past makes the list stale, and a verifier that fails closed will then reject every client instead of only the revoked one.

Check that the serial is in the copy the daemon loaded

Revoking writes an entry into the CA database, and regenerating writes a new file. Neither action reaches a running daemon that loaded the old copy at startup. Dump the list and look for the serial:

openssl crl -in intermediate.crl -noout -text | grep -i -A2 'Serial Number'
openssl ca -revoke client.crt
openssl ca -gencrl -out intermediate.crl

Then check how your implementation picks the file up. OpenVPN reads a path given to crl-verify once at startup, so a new list needs a restart or a restart signal; the same option pointed at a directory is re-read for each connection, which is the reason directory mode exists. strongSwan needs its credentials reloaded, for example with swanctl --load-creds. The file on disk being correct and the verifier using it are two different facts.

Check the policy for a missing or expired list

This is the decision many deployments never make explicitly, and it decides whether revocation protects you or locks everyone out. Fail-closed means no usable revocation information produces a rejection; fail-open means the verifier proceeds without it. strongSwan exposes the choice as strictcrlpolicy. OpenVPN with crl-verify refuses to start on a file it cannot read or parse, and an expired list surfaces as a TLS verification error about an expired CRL rather than as a silent accept.

State of the revocation dataFail-closed resultFail-open result
List missing or unreadableVerification fails, so expect an outageEvery client connects, including the revoked one
List past nextUpdateValidation errors until a fresh list is deployedOld status is trusted indefinitely
Revoked serial present in the loaded listHandshake rejected for that certificateHandshake rejected for that certificate

Whichever direction you pick, the automation matters more than the choice: a job that regenerates the CRL before nextUpdate, and an alert when the deployed copy ages past it, keeps either policy from becoming an incident. Online checking with OCSP moves freshness to a responder, but the same rule applies, because a verifier not configured to check the answer changes nothing.

Check the live session, then the copies of the key

Revocation is evaluated during certificate validation, which happens at connect and at renegotiation. An established session keeps running until it renegotiates, and OpenVPN's default is once an hour with reneg-sec 3600. If the person must be off the network now, terminate the session as well as publishing the list, then confirm the reconnect is refused.

Separate the credential from its copies. Deleting a profile on one laptop does not stop another copy of the same private key from authenticating; revocation is what makes every copy useless, and only once the earlier checks hold. Give a replacement device its own certificate so one can be revoked without touching the other.

Before calling it done: the validating component is identified, the list's issuer matches the leaf's issuer, the revoked serial sits in the copy the daemon actually loaded, the missing-or-expired policy is deliberate and monitored, and the active session was terminated rather than waited out. The formats behind all of it are specified in RFC 5280.

Makomi ya ndenge moko