Diagnosing OpenSSL TLS Certificate Verification Failures: A Practical Checklist
A step‑by‑step diagnostic guide for OpenSSL TLS certificate verification failures. Identify chain, trust store, hostname, and time issues, run targeted commands, and apply fixes without disabling security.
10 Oct 2026, 10:12 UTC

Problem: TLS Handshake Failures with OpenSSL
When a client or library that uses OpenSSL cannot establish a secure connection, the most common symptom is a line in the console that looks like:
verify error:num=20:unable to get local issuer certificate
Other typical error numbers include num=10 (certificate expired), num=9 (certificate not yet valid), and num=19 (self‑signed certificate in chain). These messages appear both when using the command‑line tool openssl s_client and when a production application fails to connect.
Recognizable Conditions
- Missing intermediate –
num=20 - Expired or not yet valid –
num=10/num=9 - Self‑signed in chain –
num=19 - Hostname mismatch – no error code in
s_clientunless-verify_hostnameis used - Trust store mis‑configured –
num=20even with a complete chain
Root Causes & Diagnostic Table
| Condition | Likely Cause | Quick Check |
|---|---|---|
| num=20 | Missing or misordered intermediate certificate on the server | Inspect Certificate chain section in s_client -showcerts |
| num=10 / num=9 | Certificate outside its validity window or wrong system clock | Check dates with openssl x509 -in cert.pem -noout -dates |
| num=19 | Chain contains a self‑signed certificate that is not the root | Verify that every certificate except the last is signed by the next one |
| Hostname mismatch | Server presents a cert that does not match the SNI or the client’s expected hostname | Run s_client -servername host -verify_hostname host |
| num=20 (client side) | Client’s default CA bundle points to the wrong location or is empty | Run openssl version -d to see the default path |
Ordered Checks
Capture the full handshake
openssl s_client -connect example.com:443 -servername example.com -showcerts
Look for the
Verify return codeline and the list of certificates.Validate the chain depth
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt -untrusted intermediates.pem server.pem
Replace the paths with your CA bundle and the intermediate file you extracted from the
s_clientoutput.Check each certificate’s dates and issuer linkage
openssl x509 -in cert.pem -noout -dates -issuer -subject
Do this for the server cert, each intermediate, and the root.
Confirm the trust store path and contents
openssl version -a
Verify that the listed
OPENSSL_CONForCApathpoints to a bundle that contains the expected root.Test hostname verification
openssl s_client -connect example.com:443 -servername example.com -verify_hostname example.com
If this fails while the earlier run succeeded, the application’s hostname check is stricter.
Re‑run with the application’s TLS settings
Use the same protocol version and cipher suite constraints as the application (e.g.,
-tls1_2or-cipher).
Fixes Tied to Findings
- Missing intermediate – Deploy the full chain on the server. Most web servers accept a concatenated file of server cert followed by intermediates.
- Expired or not yet valid – Renew the certificate or adjust the system clock.
- Self‑signed in chain – Remove the self‑signed cert from the chain or replace it with the proper intermediate.
- Hostname mismatch – Configure the server to present a cert whose Common Name (CN) or Subject Alternative Name (SAN) matches the requested hostname.
- Trust store mis‑configured – Point the client to the correct CA bundle with
-CAfileor-CApath, or install the bundle in the default location. - Protocol or cipher mismatch – Update the server or client to support the same TLS version and cipher suites.
Escalation Criteria
- Failure occurs only with a specific TLS version or cipher suite – investigate policy or legacy algorithm support.
- Private or internal CAs are involved – verify cross‑signing chains and that the client trusts the intermediate.
- Packet capture shows the handshake aborting before the server sends the certificate – suspect a middlebox or firewall interfering.
- All local checks pass but the application still fails – review application‑level pinning or custom verification callbacks.
Concrete Example: Fixing a Missing Intermediate
Assume example.com returns num=20 when connecting with s_client. The output shows only the server certificate:
-----BEGIN CERTIFICATE----- MIID... (server cert) -----END CERTIFICATE-----
Steps:
Download the intermediate from the CA’s website or extract it from the
s_clientoutput if present further down.Create a file
chain.pemcontaining the server cert followed by the intermediate:cat server.pem intermediate.pem > chain.pem
Configure the web server (e.g., Apache
SSLCertificateFileandSSLCertificateChainFile) to usechain.pem.Restart the server and re‑run:
openssl s_client -connect example.com:443 -servername example.com -showcerts
The last line should now read
Verify return code: 0 (ok).
Limitations & Practical Verification
- OpenSSL 3.x disables legacy algorithms by default; a SHA‑1 cert may fail on 3.x but succeed on 1.1.1.
- Different distributions ship different CA bundle paths; always confirm the path with
openssl version -a. - Application code may perform additional checks (pinning, custom callbacks) that
s_clientdoes not emulate. - Time‑based errors can be caused by client clock skew; verify with
dateandntpdateor equivalent.
Key Takeaways
- Start with
s_client -showcertsto see the exact chain presented by the server. - Use
openssl verifyto validate the chain against the intended trust store. - Check dates and issuer links for each certificate; an expired cert or a self‑signed intermediate is a common culprit.
- Confirm the client’s CA bundle path; a missing or empty bundle produces the same
num=20error. - Escalate only when the failure is isolated to particular TLS versions, private CAs, or network interference.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.