Error fix · Developer errors

openssl s_client "Verify return code": Every Code Explained

Read openssl s_client output correctly: what Verify return code 0, 10, 18, 19, 20, 21 and 62 mean, the right flags to use, and the fix for each result.

Error messageVerify return code: 21 (unable to verify the first certificate)

By SiteDownCheck Editorial · Updated November 4, 2026 · 5 min read

openssl s_client is the standard way to see what a TLS server really sends. Its last lines contain a verdict, Verify return code: N (message), and that one line decides whether a client such as curl, Python or Java will trust the connection. This page explains how to run the command so the verdict is meaningful, and what each code means.

The command to run

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
  • -connect host:port opens the connection.
  • -servername sends SNI. Without it, a server hosting many sites may return a default certificate for a different name, and you will debug the wrong thing.
  • -showcerts prints every certificate the server sends, in PEM.
  • </dev/null makes the command exit right after the handshake instead of waiting for input.

To get a short summary, filter the output:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | grep -E 'depth|verify|^ *[0-9] s:|^ *i:|Verification|Verify return code'

A healthy result looks like this:

depth=2 C = US, O = Example Trust, CN = Example Root
verify return:1
depth=1 C = US, O = Example Trust, CN = Example Intermediate
verify return:1
depth=0 CN = example.com
verify return:1
 0 s:CN = example.com
   i:C = US, O = Example Trust, CN = Example Intermediate
 1 s:C = US, O = Example Trust, CN = Example Intermediate
   i:C = US, O = Example Trust, CN = Example Root
Verify return code: 0 (ok)

Reading it: depth=0 is the server's own certificate, higher depths are intermediates and the root. s: is the subject and i: the issuer of each certificate sent. The chain is right when each certificate's issuer matches the next one's subject.

Two things that make the verdict misleading

  1. s_client does not check the hostname by default. A certificate for the wrong name still ends with Verify return code: 0 (ok) unless you add -verify_hostname example.com. Add it whenever you suspect a name problem.
  2. It uses OpenSSL's trust store, not your browser's or your language's. The default location varies by system; see openssl version -d. A result of 0 here does not guarantee that Java's cacerts or a Python certifi bundle trusts the chain. To test against a specific bundle, pass -CAfile /path/to/ca-bundle.pem or -CApath /etc/ssl/certs.

Add -verify_return_error to make the handshake abort on the first failure, which is what strict clients do.

What each code means

CodeMessageMeaning and fix
0okChain validated against the trust store you used.
9certificate is not yet validThe certificate's start date is in the future, or the checking machine's clock is wrong. Check date.
10certificate has expiredThe certificate is past its end date. Renew it; see renewing and automating SSL.
18self-signed certificateThe leaf certificate signs itself and is not in the trust store. Fine for a lab, not for production.
19self-signed certificate in certificate chainA certificate partway up the chain is self-signed and untrusted, typical of an inspecting proxy. See self signed certificate in certificate chain.
20unable to get local issuer certificateThe issuer of a certificate is not available locally. See unable to get local issuer certificate.
21unable to verify the first certificateUsually the server sent only the leaf and not the intermediate. See incomplete certificate chain.
62hostname mismatchAppears with -verify_hostname. The name you asked for is not in the certificate. See certificate name mismatch and SAN.

Other numbers exist. Look up any code with openssl verify -help and the X509_verify_cert manual page, which lists the full set.

Interpreting a real failure

Here is what a server that sends only its leaf certificate looks like when the root is trusted:

depth=0 CN = www.example.test
verify error:num=20:unable to get local issuer certificate
...
 0 s:CN = www.example.test
   i:CN = Test Intermediate
Verification error: unable to verify the first certificate
Verify return code: 21 (unable to verify the first certificate)

Only one certificate is listed (index 0), and its issuer is an intermediate that was never sent. That tells you the fix is on the server: serve the full chain.

Check the details of the certificate

Pipe the leaf into x509 for names and dates:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

Compare the subjectAltName list to the hostname, and notAfter to today's date. The SSL Checker reports the same facts, and the SSL Chain Debugger draws the chain.

Other useful flags

  • -tls1_2 or -tls1_3 forces a protocol version, to test what the server accepts. See also sslv3 alert handshake failure.
  • -starttls smtp (or imap, pop3, ftp) tests mail and other services that upgrade to TLS.
  • -brief prints a condensed summary on OpenSSL 1.1.1 and later.
  • -proxy host:port connects through an HTTP proxy.

Next steps

FAQ

Does "Verify return code: 0 (ok)" mean the site is correct?

It means the chain validated against the trust store you used. It does not check the hostname unless you pass -verify_hostname, and other clients may use different trust stores.

Why does s_client say "Can't use SSL_get_servername"?

That message is printed when you did not supply a usable server name, or the connection is to an IP address. Use the hostname with -servername so SNI is sent.

Why do I see several verify errors before the final code?

OpenSSL prints an error for each certificate in the chain as it checks it. The last line, the verdict, is what counts.

What does depth mean?

Depth 0 is the server certificate, depth 1 is the intermediate that signed it, and so on up to the root.

Get notified when this fix updates

Plus occasional practical tips on uptime and monitoring, nothing else. Unsubscribe anytime.

This page is general information written by our editorial team and updated when documentation changes. Servers and providers differ, so test changes safely first. See our editorial policy and disclaimer.