Verify Certificate Chains with OpenSSL Without Guessing
A browser padlock tells you nothing about why a chain was trusted, but openssl verify will show you the actual reasoning. This guide builds a repeatable way to check that a certificate chains to a trusted CA and that its identity matches the hostname you intend to use. The examples take about 10 minutes, use only temporary files, and never touch the system trust store or need root.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
Install OpenSSL and have a PEM certificate ready. In a normal deployment you will also need the CA certificate that should trust it, plus any intermediate certificates the issuer supplied. The command accepts one or more certificate files; give it none and it reads one certificate from standard input.
There is a version detail on this machine worth knowing: the installed manpage is for the Ubuntu package version 3.0.13, while the executable found in the current shell reports OpenSSL 3.6.1. Check the binary before relying on a newer option:
openssl version -a
openssl verify -help
Use the manpage installed alongside the binary you deploy; the core commands below are present in both versions.
1. Verify a certificate against a CA file
Pass the CA certificate with -CAfile, then name the certificate being checked. This is the clearest form when you have a specific trust anchor rather than wanting the host's default store:
openssl verify \
-CAfile /path/to/trusted-ca.pem \
/path/to/server.pem
Success ends with the certificate path followed by OK:
/path/to/server.pem: OK
A successful chain check covers the certificate path and its verification rules. It does not, on its own, prove the certificate is suitable for a particular DNS name; add an explicit hostname check for that.
Checkpoint
If the result looks unexpectedly good, make sure the CA file is really the one you intended. Without -CAfile, OpenSSL can load its configured default CA file, directory and store, so use -no-CAfile, -no-CApath and -no-CAstore to disable those defaults and test only the sources you named. These switches affect only this command; they change nothing on the system.
For a private CA, prefer an explicit file in an access-controlled location. Do not add a private CA to a machine-wide store merely to make one test pass: that changes the trust decision for other software and is a separate administrative action.
2. Supply intermediates without trusting them
An issuing CA is usually an intermediate, not a trust anchor. Give it with -untrusted: OpenSSL may use it to build the chain, but it is never automatically treated as trusted:
openssl verify \
-CAfile /path/to/root-ca.pem \
-untrusted /path/to/intermediate-ca.pem \
/path/to/server.pem
This distinction is a common source of confusing tests. -trusted supplies certificates with trust settings; -untrusted supplies certificates for chain building. If the server certificate is part of a bundle, split out the leaf and intermediates so you know exactly which input is doing what.
Ask OpenSSL to show the chain it built when you need to audit the result:
openssl verify -show_chain \
-CAfile /path/to/root-ca.pem \
-untrusted /path/to/intermediate-ca.pem \
/path/to/server.pem
Typical output puts the leaf at depth 0 and the trust anchor at the greatest depth, with entries introduced from the untrusted input marked (untrusted). The exact subject formatting depends on the OpenSSL version and name options.
3. Check the identity as well as the chain
For a TLS server certificate, use -verify_hostname with the name a client will send. This checks the certificate identity, including its subject alternative names:
openssl verify \
-CAfile /path/to/root-ca.pem \
-verify_hostname service.example.test \
/path/to/server.pem
Use -verify_ip for an IP address and -verify_email for an email identity. Do not substitute a common name check for the hostname option: the command's verification mode is the meaningful test of how a TLS client identifies the peer.
For a harmless local test, create a short-lived certificate whose subject alternative name is service.example.test, then run the command above. With the matching name, expect OK; with a different one, expect a failure similar to:
error 62 at 0 depth lookup: hostname mismatch
error /path/to/server.pem: verification failed
The exit status matters in scripts: success is zero, a failed verification is non-zero. Capture it immediately if later diagnostic commands must run.
4. Read a failure at the right depth
OpenSSL can report several problems in one run. A diagnostic normally names the certificate, gives an error number, states the depth, and adds a readable reason.
| Depth | Meaning |
|---|---|
| 0 | The target leaf certificate. |
| 1 | The CA that signed the leaf. |
| 2 and above | Higher CAs while walking towards the trust anchor. |
A hostname mismatch at depth 0 points at the identity in the leaf. An invalid CA at depth 1 usually means the supplied intermediate is not a valid issuer for the leaf. An inability to get the local issuer certificate often means an intermediate is missing or the selected trust source is wrong. Check the certificate subjects and issuers with:
openssl x509 -in /path/to/server.pem -noout -subject -issuer -dates
openssl x509 -in /path/to/intermediate-ca.pem -noout -subject -issuer
Keep the error number when reporting a problem. The text reads easier, but the number gives you a stable starting point for looking up the matching X509 verification error in the OpenSSL documentation.
5. Know the escape hatches from the repairs
Warning
Verification is read-only, and options such as -no_check_time, -partial_chain and -ignore_critical change what the command accepts. They are diagnostic exceptions, not repairs; do not use them in a production health check unless the policy is deliberate and documented. Ignoring certificate time in particular can hide an expired or not-yet-valid certificate.
-crl_download may fetch revocation data from certificate distribution-point entries. Treat that as network activity: it can be slow, unavailable, or dependent on the certificate's external URLs. CRL checking is never enabled merely because a certificate contains a CRL pointer; request it explicitly with -crl_check or -crl_check_all and provide suitable CRLs with -CRLfile when that is your policy.
Recovery
Nothing in these examples changes a service, certificate, key or trust store. If you test the wrong CA by accident, correct the command's input paths and rerun it. If you changed a configuration outside this guide while troubleshooting, restore that file from its backup or undo only the documented change; never delete a system trust directory to force a result.
Done means
- Trust source chosen deliberately. The intended CA was selected explicitly, or the default trust sources were consciously reviewed.
- Intermediates handled correctly. They were supplied as
-untrustedinputs, never silently promoted to trust anchors. - Chain verified. The certificate chain returned
OKwith exit status zero. - Identity checked separately. The expected hostname, IP address or email identity was tested on its own.
- Exceptions documented. Any escape-hatch option or network-based CRL lookup is recorded rather than used as a shortcut.