Verify X.509 Chains with OpenSSL Without Losing Trust
A certificate chain that "just verifies" can hide exactly which trust anchor did the work, and these openssl verify options are how you pin that down. This guide checks a certificate against a trust store, adds an intermediate without trusting it, tests a hostname, and digs into time or policy failures. The examples are read-only and take about 10 minutes if you already have a PEM certificate and, where needed, its CA files.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
You need OpenSSL and a target certificate in PEM format, plus possibly a CA bundle and one or more intermediate certificates. Verification does not prove a private key is protected or that a server is reachable: it answers one narrower question, can this certificate chain be built and accepted under the options you supplied.
Check which executable and manpage you are using:
command -v openssl
openssl version
man openssl-verification-options
On the system used for this guide, the installed Ubuntu package is OpenSSL 3.0.13, while the executable on PATH reports 3.6.1. The option names below are present in the local openssl verify -help output, but check your own command if the versions differ.
1. Run the baseline check
Start with the default trust configuration. Replace the path with the certificate you are checking:
openssl verify /path/to/server.pem
A successful result names the certificate and ends with OK:
/path/to/server.pem: OK
OpenSSL may load a system default CA file, directory or store. The manpage is explicit that OpenSSL itself provides no universal set of trust anchors; distributions normally configure one for you. If this check fails, do not reach straight for disabling checks. Work out first which trust material is missing or unsuitable.
2. Make the trust source explicit
Use -CAfile when you have a PEM bundle of trusted CA certificates:
openssl verify \
-CAfile /path/to/trust-bundle.pem \
/path/to/server.pem
Use -CApath for a hashed directory of CA certificates instead. Filenames there must use the X.509 subject-name hash, so an ordinary directory of randomly named PEM files may not work; run openssl rehash if you maintain that directory:
openssl verify \
-CApath /path/to/hashed-ca-directory \
/path/to/server.pem
For a narrow test, -trusted supplies the only trust anchors and implies -no-CAfile, -no-CApath and -no-CAstore. That is handy for repeatable tests, but it changes the trust boundary, so do not confuse it with -untrusted: that one supplies intermediates for chain building, not roots that can terminate trust.
3. Add intermediates without trusting them
When the target certificate does not carry its intermediate CA, provide a file of one or more intermediate certificates:
openssl verify \
-CAfile /path/to/root-bundle.pem \
-untrusted /path/to/intermediates.pem \
/path/to/server.pem
Ask OpenSSL to show the chain it actually built:
openssl verify \
-show_chain \
-CAfile /path/to/root-bundle.pem \
-untrusted /path/to/intermediates.pem \
/path/to/server.pem
Read the result as a chain-building problem. The trust file lists certificates allowed to anchor the result; the untrusted file lists candidates that can link the leaf to an anchor. Trusted certificates are searched first by default, so a locally supplied anchor can affect which valid path gets picked.
4. Check the identity and intended use
A valid chain is not automatically valid for a particular peer. Use -verify_hostname for a DNS name, -verify_ip for an IP address, or -verify_email for an email identity:
openssl verify \
-verify_hostname example.test \
-CAfile /path/to/trust-bundle.pem \
/path/to/server.pem
Use -purpose sslserver when checking a certificate for a TLS server, or -purpose sslclient for a TLS client. Purpose checks examine extensions such as basic constraints, key usage and extended key usage; when an EKU extension is present, it limits the permitted uses rather than adding a second independent approval:
openssl verify \
-purpose sslserver \
-verify_hostname example.test \
-CAfile /path/to/trust-bundle.pem \
/path/to/server.pem
5. Reproduce time and strength failures
Certificate validity is checked against the current system time. To reproduce a historical result, pass a Unix timestamp with -attime:
openssl verify \
-attime 1704067200 \
-CAfile /path/to/trust-bundle.pem \
/path/to/server.pem
Warning
-no_check_time suppresses validity-period checks. It is a diagnostic escape hatch, not a repair; never use it to claim an expired certificate is acceptable.
-auth_level applies a certificate-chain security level. The manpage describes level 1 as broadly interoperable, rejecting for example MD5 signatures or RSA keys shorter than 1024 bits. Raising the level can expose weak legacy material; lowering checks can hide it. Record whichever level you choose in tests and configuration, so a later operator does not mistake it for a default.
6. Tighten checks deliberately
Use -x509_strict to disable compatibility workarounds for certificates that do not comply with RFC 5280. It can reject certificates with issues such as a non-critical CA basic-constraints extension or missing required key usage, which is useful for acceptance testing and migration work but can expose certificates that older software happily accepted:
openssl verify \
-x509_strict \
-CAfile /path/to/trust-bundle.pem \
/path/to/server.pem
Use -crl_check or -crl_check_all only when the required CRLs are actually available. The first checks the end-entity certificate; the second checks every certificate in the chain. A missing usable CRL is an error, so these options never fetch or invent revocation data for you.
Common traps
- Unknown issuer: confirm the correct root is in
-CAfileor-CApath, then supply the missing intermediate with-untrusted. - Hostname failure: inspect the certificate SAN with
openssl x509 -in /path/to/server.pem -noout -subject -ext subjectAltName. Fix the name or certificate; do not remove hostname checking. - Unexpected trust: use
-trustedwith-no-CAfile -no-CApath -no-CAstoreto isolate the test to named anchors. - Changed results after a CA update: rerun with an explicit trust file and record its checksum. Trust stores are inputs, not neutral background.
Recovery
The options above never modify certificates or trust stores. If you do run openssl rehash, keep a backup of the directory and remove only the generated hash links to undo that change. Do not replace a system CA bundle during troubleshooting.
Done means
- Baseline checked. The default check returns
OK, or the failure has a named cause. - Trust anchor explicit. Any repeatable or security-sensitive test names its trust source directly.
- Intermediates handled correctly. They were supplied with
-untrusted, never silently promoted to trust anchors. - Identity checked. Hostname, IP, email, purpose, time, revocation and strictness checks match the question you are actually asking.
- Versions recorded. You noted the OpenSSL executable version and the trust-store inputs used.