Skip to content

Troubleshooting

Every TLS complaint in the world reduces, at the certificate layer, to a short list of failures — and every one of them is already on your disk if you built the lab. This chapter produces each one twice: once with openssl verify (the paper check), once at a live handshake with s_server / s_client where that adds anything, and reads the message the way chapter 3 taught: the human sentence, last, first.

Throughout: add -verify_return_error to s_client when testing. Without it, s_client reports verification failures and connects anyway — behaviour inherited from its debugging purpose, and the cause of more than one "but OpenSSL said it worked" ghost.

1. Unknown issuer — "unable to get local issuer certificate"

The verifier cannot find the certificate that signed yours in anything it trusts.

$ openssl verify server.cert.pem
CN = server.example.test, O = Example Lab
error 20 at 0 depth lookup: unable to get local issuer certificate
error server.cert.pem: verification failed

Error 20. The usual real-world cause is not an evil certificate but an institutional one: the issuer is a private CA (your company's, your lab's) that this machine has simply never been told about. The fix is the -CAfile / -CApath that hands the verifier its root — or, in a real program, installing that root in the system store properly. In the lab, offering the root and the intermediate settles it:

$ openssl verify -CAfile root/root.cert.pem -untrusted inter/inter.cert.pem server.cert.pem
server.cert.pem: OK

The same failure wears a sibling's face when the issuer is known but a link is missing — that is failure 6, below.

2. Self-signed — error 18

$ openssl verify selfsigned.cert.pem
CN = server.example.test
error 18 at 0 depth lookup: self-signed certificate
error selfsigned.cert.pem: verification failed

Live, a server presenting it gets the same number in the handshake (Verify return code: 18). Error 18 means the certificate's issuer is itself — chapter 6's circular vouching. The fix is never a setting on the certificate; it is trust, arranged on purpose: install the certificate (or a CA of your own, chapter 7) into the client's store, or get the certificate issued by a CA the client already trusts. If the self-signed certificate is unexpected — a name you pay a real CA for — you are looking at the wrong server, a misconfigured one, or an interception, and the correct fix is at the server.

3. In the chain, but the root isn't trusted — error 19

Present the full lab chain to a client that trusts nothing (s_client with no -CAfile) and the number changes:

verify error:num=19:self-signed certificate in certificate chain
Verify return code: 19 (self-signed certificate in certificate chain)

Error 19 is error 18 one level up: the walk reached a self-signed root — fine — but that root is not in this client's store. It is the normal state of a private-CA deployment before the root is distributed, and the reason chapter 7's root is the one file you give everyone, while the intermediate's key stays home. Browsers shipped by an employer do this distribution for the company CA; your lab does it with -CAfile.

4. Expired — error 10

$ openssl verify -CAfile root/root.cert.pem -untrusted inter/inter.cert.pem expired.cert.pem
CN = server.example.test, O = Example Lab
error 10 at 0 depth lookup: certificate has expired
error expired.cert.pem: verification failed
verify error:num=10:certificate has expired
Verify return code: 10 (certificate has expired)

Error 10 is the one failure with a clock in it. Two readings before renewing anything: check the certificate's dates (chapter 4) — an expired intermediate fails its whole family, and openssl verify reporting the leaf at "0 depth" doesn't exempt the chain — and check the verifying machine's clock, since a client with a wrong date manufactures error 10 (and its twin, error 9: certificate is not yet valid) against perfect certificates. The lab's specimen was made with openssl ca -startdate 20240101000000Z -enddate 20250101000000Z; its real-world counterpart arrives by itself, on a schedule, usually during a holiday.

5. Hostname mismatch — error 62

The certificate is valid, trusted, in date — and for a different name.

$ openssl verify -CAfile root/root.cert.pem -untrusted inter/inter.cert.pem -verify_hostname server.example.test wronghost.cert.pem
CN = other.example.test, O = Example Lab
error 62 at 0 depth lookup: hostname mismatch
error wronghost.cert.pem: verification failed
$ openssl s_client -connect 127.0.0.1:8443 -CAfile root/root.cert.pem -verify_hostname other.example.test -verify_return_error
verify error:num=62:hostname mismatch

Error 62. Remember what is being compared: the name the client asked for (-verify_hostname, or the hostname it dialled) against the certificate's SAN list — chapter 4's -ext subjectAltName shows the list it failed to be in. Causes in the wild, in order: the certificate was issued for a different name than the service now answers to; several names share one address and the server picked the wrong certificate (SNI, chapter 8); or www.example.test is being served the certificate for example.test without the www in its SANs. The fix is always on the certificate or the server's choice of it — never on the client's insistence. Ask the same verifier for the name the certificate actually carries and the identical file passes:

$ openssl verify -CAfile root/root.cert.pem -untrusted inter/inter.cert.pem -verify_hostname other.example.test wronghost.cert.pem
wronghost.cert.pem: OK

Failure 1 with the anchor present but the middle missing. Serve the lab certificate without -cert_chain, to a client that trusts the root:

verify error:num=20:unable to get local issuer certificate
Verify return code: 20 (unable to get local issuer certificate)

The client holds the root; the server holds the intermediate; neither can complete the walk because the one certificate nobody shipped is the one that joins them. The diagnostic question is "what did the server actually present?" — the Certificate chain block of any s_client run lists it, and one block with only 0 s:... in it is this failure photographed. The fix is server-side: present the chain bundle (chapter 10's server-chain.pem, or -cert_chain), because clients cannot be expected to fetch your middle links — some do, which is why this failure classically appears on some clients and not others, and wastes the most time of any in this chapter.

The general drill

For any certificate complaint, in order: read the certificate (chapter 4 — subject, issuer, dates, SANs); reproduce with verify against a known root; reproduce with s_client -verify_return_error against the live service and read the chain block; believe the number, not the prose around it. The five numbers here — 10, 18, 19, 20, 62 — cover the overwhelming majority of what production will ever show you, and now each one has a face you made yourself.