How-to guide · Inspect and check

How to check the certificate chain a server sends

In one sentence

You will have listed every certificate a server sends, checked their order and links, and verified the chain with a strict client instead of a browser.

Task
Inspect and check
Platforms
  • Linux
  • macOS
  • Windows
  • Java
Who it is for
Level
Practitioner · 6 steps
On this page
  1. Before you start
  2. Steps
  3. 1. List the certificates the server sends
  4. 2. Read the chain
  5. 3. Let OpenSSL verify it
  6. 4. Save the chain and check it offline
  7. 5. Check servers on other ports and protocols
  8. 6. On Windows or with Java
  9. Check it worked
  10. If something goes wrong
  11. Why this works
  12. Go deeper

Before you start

  • OpenSSL 1.1.1 or later (Linux, macOS with Homebrew, or Git Bash on Windows). Java users can use keytool instead (step 6).
  • The host name and port of the server, and network access to it from where you run the command.
  • For an internal server: your organisation’s root certificate as a PEM file, to use with -CAfile.

Why bother? A browser hides most chain mistakes: it can download a missing intermediate certificate or reuse one from another site. curl on Linux, Java, Python, many mobile apps and partner systems cannot, so they fail while your browser test passes.

Steps

1. List the certificates the server sends

openssl s_client -connect www.rfc-editor.org:443 -servername www.rfc-editor.org -showcerts </dev/null 2>/dev/null \
  | grep -E '^ *[0-9]+ s:|^ +[iav]:|Verify return code'

This is the live chain of www.rfc-editor.org on 20 October 2026:

 0 s:CN=rfc-editor.org
   i:C=US, O=Google Trust Services, CN=WE1
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA256
   v:NotBefore: Oct 10 11:18:19 2026 GMT; NotAfter: Jan  8 12:18:06 2027 GMT
 1 s:C=US, O=Google Trust Services, CN=WE1
   i:C=US, O=Google Trust Services LLC, CN=GTS Root R4
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA384
   v:NotBefore: Dec 13 09:00:00 2023 GMT; NotAfter: Feb 20 14:00:00 2029 GMT
 2 s:C=US, O=Google Trust Services LLC, CN=GTS Root R4
   i:C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
   a:PKEY: id-ecPublicKey, 384 (bit); sigalg: RSA-SHA256
   v:NotBefore: Nov 15 03:43:21 2023 GMT; NotAfter: Jan 28 00:00:42 2028 GMT
Verify return code: 0 (ok)

Each numbered entry is one certificate, in the order the server sent it: s: is the subject, i: the issuer, a: the key and signature algorithm, and v: the validity dates. OpenSSL 3.0 prints the same lines with spaces around =. Without the grep, you also get each certificate’s PEM block.

As the OpenSSL manual says about -showcerts, “It is not a verified chain”: it shows what arrived, not what your client trusted.

2. Read the chain

Check four things:

  1. Entry 0 is your server’s certificate (the leaf), for the name you connected to.
  2. Every i: equals the next entry’s s:. Above, the leaf was issued by WE1, WE1 by GTS Root R4, and so on. A gap means a missing or wrong certificate.
  3. The intermediates are there. If you see only entry 0 and its issuer is not a root in your trust store, the intermediate is missing.
  4. The root is optional. The client must already have it. The TLS 1.3 specification, RFC 9846, lets the server omit it (section 4.5.1).

Entry 2 above is a cross-signed certificate: Google’s root GTS Root R4, signed by the older GlobalSign Root CA. Clients that already trust GTS Root R4 stop at entry 1; older clients follow the cross-sign to GlobalSign. Two roads to the same root explains why servers send these.

3. Let OpenSSL verify it

The last line, Verify return code, is OpenSSL’s verdict against its own trust store. Here is a test server that sends only its leaf:

depth=0 C=BE, O=Example Shop, CN=www.example.com
verify error:num=20:unable to get local issuer certificate
...
depth=0 C=BE, O=Example Shop, CN=www.example.com
verify error:num=21:unable to verify the first certificate
...
Certificate chain
 0 s:C=BE, O=Example Shop, CN=www.example.com
   i:C=BE, O=Keystone Demo, CN=Keystone Demo Issuing CA 1
...
Verify return code: 21 (unable to verify the first certificate)

One entry, and errors 20 and 21: the intermediate is missing. The same server with the intermediate added, checked against our private root:

openssl s_client -connect 127.0.0.1:8444 -servername www.example.com -CAfile root.crt -verify_return_error </dev/null
depth=2 C=BE, O=Keystone Demo, CN=Keystone Demo Root CA
verify return:1
depth=1 C=BE, O=Keystone Demo, CN=Keystone Demo Issuing CA 1
verify return:1
depth=0 C=BE, O=Example Shop, CN=www.example.com
verify return:1
...
Verify return code: 0 (ok)

Two options make the test strict:

  • -verify_return_error stops the handshake at the first error, as a real client would. Without it, s_client reports the error and carries on.
  • -CAfile root.crt tells OpenSSL which root to trust for a private CA. Give it only the root: if you add the intermediate too, you hide the very mistake you are looking for.

4. Save the chain and check it offline

Save every certificate the server sent into one file:

openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts </dev/null 2>/dev/null \
  | sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' > sent.pem

Then verify the first certificate in it, offering the rest as intermediates. Our test chain ends in a private root, so we pass it with -CAfile; for a public CA, leave that option out:

openssl verify -show_chain -CAfile root.crt -untrusted sent.pem sent.pem
sent.pem: OK
Chain:
depth=0: C=BE, O=Example Shop, CN=www.example.com (untrusted)
depth=1: C=BE, O=Keystone Demo, CN=Keystone Demo Issuing CA 1 (untrusted)
depth=2: C=BE, O=Keystone Demo, CN=Keystone Demo Root CA

“Untrusted” means “supplied, not a trust anchor”, which is right for a leaf and its intermediates. You can also paste sent.pem into the chain checker:

Try it: Chain checkerOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

Every certificate the server sends, leaf first. The output of openssl s_client -showcerts -connect example.com:443 works as it is: we ignore the text around the certificates.

5. Check servers on other ports and protocols

Mail, LDAP and database servers often start in plain text and switch to TLS. Tell s_client the protocol:

openssl s_client -connect smtp.gmail.com:587 -starttls smtp -showcerts </dev/null 2>/dev/null \
  | grep -E '^ *[0-9]+ s:|^ +i:|Verify return code'
 0 s:CN=smtp.gmail.com
   i:C=US, O=Google Trust Services, CN=WE2
 1 s:C=US, O=Google Trust Services, CN=WE2
   i:C=US, O=Google Trust Services LLC, CN=GTS Root R4
 2 s:C=US, O=Google Trust Services LLC, CN=GTS Root R4
   i:C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
Verify return code: 0 (ok)

-starttls accepts smtp, imap, pop3, ftp, ldap, postgres, mysql and a few more. If your network only allows web traffic through a proxy, add -proxy proxy.example.com:8080.

6. On Windows or with Java

Git for Windows includes OpenSSL, so every command above runs in Git Bash. With a JDK, keytool prints each certificate the server sends with its SHA-256 fingerprint:

keytool -printcert -sslserver www.rfc-editor.org:443
Certificate #0
Owner: CN=rfc-editor.org
Issuer: CN=WE1, O=Google Trust Services, C=US
...
Certificate #1
Owner: CN=WE1, O=Google Trust Services, C=US
Issuer: CN=GTS Root R4, O=Google Trust Services LLC, C=US

Check it worked

Run the strict test from outside your own network, against the public name, after every certificate change:

openssl s_client -connect www.example.com:443 -servername www.example.com \
  -verify_return_error -verify_hostname www.example.com </dev/null 2>&1 | grep 'Verify return code'

Verify return code: 0 (ok) means the chain links up to a root OpenSSL trusts, every certificate is in date, and the name matches. For an internal server, add -CAfile with your root.

If something goes wrong

Why this works

In TLS, the server sends its certificate chain in one message, and the client must link each certificate to its issuer until it reaches a root in its own trust store. OpenSSL, like curl on Linux, Python and Java, uses only what the server sent plus its trust store; it does not download missing intermediates. That makes it a faithful stand-in for your strictest clients. On Windows, curl.exe and other tools that use the Windows chain engine can download a missing intermediate, so they may pass where OpenSSL fails. Certificate chains and path validation explains the linking and checking, Who do we trust? explains which store each client uses, and Two roads to the same root explains why chains differ between clients.

Go deeper

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9