Part IV: Trust and HTTPS · Chapter 13

Certificate chains and path validation

In one sentence

Servers must send their leaf and intermediate certificates; clients link them by name and key identifier up to a trusted root and check every signature, date and constraint.

Who is this for

Level: Practitioner

On this page
  1. The idea
  2. How it really works
  3. At work
  4. Common mistakes
  5. Why it matters to the business
  6. Check yourself
  7. Go deeper

The idea

  • Beginner

Your website’s certificate was not signed by a root directly. Almost every public certificate hangs at the end of a short certificate chain:

  1. the leaf: your server’s certificate, for www.example.com;
  2. one or more intermediate certificates: CAs that sign leaf certificates day to day;
  3. the root: the CA certificate that sits in your trust store (chapter 12).

Why the middle step? A root’s private key is the most valuable thing a CA owns. CAs keep it offline in a vault and use it rarely, mainly to sign intermediates. The intermediates do the daily work. If an intermediate ever has to be replaced, the CA signs a new one with the root, and nobody’s trust store has to change.

To check your certificate, a client must walk the chain: your leaf was signed by the intermediate, the intermediate was signed by the root, and the root is in the client’s trust store. The client already has the root. It needs your server to hand over the intermediate, because most clients have no other way to get it.

That is the single most common certificate mistake on the internet: a server that sends its own certificate but not the intermediate. The confusing part is that browsers often hide the mistake. They can fetch a missing intermediate on their own, or remember one from another website. So the site looks fine in your browser, while curl, Java, Python, mobile apps and partners’ servers fail with “unable to get local issuer certificate”.

How it really works

  • Developer
  • Expert

For each certificate, the client looks for the certificate of its issuer. Two fields connect them (RFC 5280):

Our test chain from chapter 8 shows both links:

Certificate Subject Issuer SKI AKI
Leaf CN=www.example.com CN=Keystone Demo Issuing CA 1 B4:7C:08:… 73:25:6F:…
Intermediate CN=Keystone Demo Issuing CA 1 CN=Keystone Demo Root CA 73:25:6F:… A8:79:88:…
Root CN=Keystone Demo Root CA CN=Keystone Demo Root CA A8:79:88:… A8:79:88:…

The client repeats the step until it reaches a certificate whose key is a trust anchor in its store. Finding that route among many candidates is called path building. When several routes exist, as with cross-signing (chapter 14), good clients try alternatives (RFC 4158 describes the techniques).

A certificate chain from the leaf through the intermediate to a root in the trust store Client's trust store Keystone Demo Root CA ✓ found in the trust store root issuer = subject above AKI = SKI above Keystone Demo Issuing CA 1 ✓ signature valid, CA:TRUE intermediate issuer = subject above AKI = SKI above www.example.com ✓ signature valid, name matches leaf
A client links each certificate to its issuer until it reaches a root that is already in its trust store.

What does path validation check?

Once a path is found, the client validates it from the trust anchor down to the leaf. RFC 5280 section 6.1 defines the algorithm. For every certificate in the path, the client checks:

  • the signature, with the public key of the certificate above it;
  • the validity dates, against its own clock;
  • revocation, if it checks at all (chapter 16);
  • name constraints set by any CA above it.

For every CA certificate in the path, it also checks that cA is TRUE, that the key usage allows certificate signing, and that no pathLenConstraint is exceeded (chapter 9). Any unknown critical extension stops the process. On top of RFC 5280, TLS clients check that the leaf allows server authentication and that it names the host they wanted.

A single failure anywhere fails the whole path. An expired intermediate breaks every certificate below it, however new those are.

What must the server send?

The TLS 1.3 specification, RFC 9846, sets the rules for the server’s Certificate message (section 4.5.1):

  • “The sender’s certificate MUST come in the first CertificateEntry in the list.”
  • “Each following certificate SHOULD directly certify the one immediately preceding it.”
  • The root “MAY be omitted from the chain”, because the client must already have it.

So a correct server sends its leaf first, then its intermediates, in order, and usually not the root. Sending the root does no harm in most clients, but it wastes bytes and cannot create trust. The same section asks clients to “be prepared to handle potentially extraneous certificates and arbitrary orderings”, so modern clients forgive extra or shuffled intermediates. They cannot forgive a missing one.

Why does it work in my browser but not in curl?

Many certificates carry an Authority Information Access (AIA) extension with a “CA Issuers” URL. It points to a copy of the issuer’s certificate and is “intended to aid certificate users in the selection of a certification path” (section 4.2.2.1). Clients differ in how they use it:

  • Chrome, Edge and Safari can download a missing intermediate from that URL, and browsers also reuse intermediates they saw on other sites. Chrome’s certificate verifier, which Edge also builds on, does this AIA fetching (Chromium source), and Apple’s platform verifier, used by Safari, fetches missing intermediates by default for TLS (Apple developer documentation). The Windows certificate chain engine behind many Windows apps downloads missing issuers too, and .NET does so unless an application sets DisableCertificateDownloads.
  • Firefox does not fetch AIA. Instead, it preloads the intermediates that public CAs disclose to the Common CA Database, so it also hides most missing intermediates of public CAs. For a private CA’s missing intermediate, Firefox shows SEC_ERROR_UNKNOWN_ISSUER.
  • OpenSSL and the tools built on it (curl on Linux, Python, Node.js, PHP, and others) do not fetch anything. Java does not either, unless the system property com.sun.security.enableAIAcaIssuers is set to true.

Not every certificate has an AIA URL in the first place: the Baseline Requirements only recommend, and no longer require, the Authority Information Access (AIA) extension in publicly trusted TLS certificates (since 4 October 2026). The only reliable fix is on the server: send the full chain.

A server that sends only its leaf: browsers often repair the chain, other clients fail Server sends only the leaf ✗ no intermediate ✓ Browser: works downloads the issuer via AIA or reuses a copy it has seen ✗ curl, Python, Node.js: fail unable to get local issuer certificate ✗ Java: fails PKIX path building failed Fix: the server sends the leaf, then the intermediate.
A missing intermediate: browsers often repair it, most other clients fail. Fix the server, not the clients.

At work

  • Developer
  • IT ops

How do I see the chain a server sends?

openssl s_client -showcerts prints every certificate the server sent, in the order it sent them. As the OpenSSL manual warns, “It is not a verified chain”: it shows what arrived, not what was trusted. Here is letsencrypt.org in October 2026, with the PEM blocks removed:

openssl s_client -connect letsencrypt.org:443 -servername letsencrypt.org -showcerts </dev/null
Certificate chain
 0 s:CN=letsencrypt.org
   i:C=US, O=Let's Encrypt, CN=YE2
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA384
   v:NotBefore: Sep  4 14:34:32 2026 GMT; NotAfter: Dec  3 14:34:31 2026 GMT
 1 s:C=US, O=Let's Encrypt, CN=YE2
   i:C=US, O=ISRG, CN=Root YE
   ...
 2 s:C=US, O=ISRG, CN=Root YE
   i:C=US, O=Internet Security Research Group, CN=ISRG Root X2
   ...
 3 s:C=US, O=Internet Security Research Group, CN=ISRG Root X2
   i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
   ...

Read the s: (subject) and i: (issuer) lines as pairs: each certificate’s issuer is the next one’s subject. Certificate 0 is the leaf, and the others are intermediates and cross-signed certificates, which chapter 14 explains. A healthy chain always starts with the leaf and links without gaps.

What does a missing intermediate look like?

We ran a test server that sends only its leaf, www.example.com, and gave every client the right root, so the intermediate is the only missing piece:

openssl s_client -connect 127.0.0.1:8443 -servername www.example.com -CAfile root.crt </dev/null
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
...
Verify return code: 21 (unable to verify the first certificate)

The chain has a single entry. OpenSSL reports error 20, it cannot find the leaf’s issuer, and error 21, so it cannot verify even the first certificate. Other clients say the same thing differently:

# Python 3.13
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)

# Node.js 23
UNABLE_TO_VERIFY_LEAF_SIGNATURE: unable to verify the first certificate; if the root CA is installed locally, try running Node.js with --use-system-ca

curl prints SSL certificate problem: unable to get local issuer certificate, and Java prints PKIX path building failed. The error pages for Python and Node.js walk through each client’s options. Note that Node’s hint about --use-system-ca does not help here: the root was already trusted, and the intermediate is simply missing. After we configured the server to send the intermediate as well, the same command showed:

depth=2 C=BE, O=Keystone Demo, CN=Keystone Demo Root CA
depth=1 C=BE, O=Keystone Demo, CN=Keystone Demo Issuing CA 1
depth=0 C=BE, O=Example Shop, CN=www.example.com
...
Certificate chain
 0 s:C=BE, O=Example Shop, CN=www.example.com
...
 1 s:C=BE, O=Keystone Demo, CN=Keystone Demo Issuing CA 1
...
Verify return code: 0 (ok)

How do I check a chain file before deploying it?

openssl verify checks files offline. Pass the trusted root with -CAfile and the intermediates with -untrusted:

openssl verify -CAfile root.crt www.crt
openssl verify -show_chain -CAfile root.crt -untrusted int.crt www.crt
C=BE, O=Example Shop, CN=www.example.com
error 20 at 0 depth lookup: unable to get local issuer certificate
error www.crt: verification failed

www.crt: 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” here means “supplied, not a trust anchor”, which is exactly right for a leaf and its intermediate.

How do I fix it on my server?

Give the server a file or setting that contains the leaf followed by its intermediates. Your CA’s download or your ACME client usually provides one, often called fullchain.pem.

nginx: ssl_certificate takes the full chain. In nginx’s words, “The server certificate must appear before the chained certificates in the combined file” (nginx docs):

cat www.example.com.crt intermediate.crt > www.example.com.fullchain.crt
ssl_certificate     /etc/nginx/tls/www.example.com.fullchain.crt;
ssl_certificate_key /etc/nginx/tls/www.example.com.key;

If the order is wrong, nginx refuses to start with key values mismatch, because it pairs the key with the first certificate in the file. With certbot, point ssl_certificate at fullchain.pem, not cert.pem.

Apache 2.4.8 and later: put the leaf and its intermediates, “sorted from leaf to root”, in the file named by SSLCertificateFile. SSLCertificateChainFile is deprecated (mod_ssl docs).

IIS: IIS builds the chain from the Windows certificate store of the local computer, so import the intermediate into Intermediate Certification Authorities on the server, and update it whenever you renew (Microsoft docs):

Import-Certificate -FilePath .\intermediate.crt -CertStoreLocation Cert:\LocalMachine\CA

Load balancers, CDNs and Kubernetes: most have a separate “certificate chain” field, or expect the full chain in the certificate field. Check after every renewal: CAs change intermediates from time to time, and an old chain file with a new leaf fails too.

Then test from outside with a strict client, not a browser:

openssl s_client -connect www.example.com:443 -servername www.example.com -verify_return_error </dev/null

Check yours

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.

The sample shows a server that sends only its leaf. Paste your own server’s output from openssl s_client -showcerts to see which link is missing, whether the order is right, and which root the chain reaches.

Common mistakes

  • “It works in my browser, so the chain is fine.” Browsers fetch or remember missing intermediates. Test with openssl s_client, or curl on Linux, which do not. (Windows curl.exe uses Schannel and may fetch the intermediate like a browser.)
  • “The server should send the root too.” The client must already have the root. Sending it adds nothing and cannot create trust.
  • “We can fix it by adding the intermediate to every client.” That works until the CA changes intermediates. Fix the one server instead of a thousand clients.
  • “The order of the certificates doesn’t matter.” The leaf must come first. Many clients forgive the rest, but servers such as nginx pair the key with the first certificate.
  • “Intermediates never change.” CAs rotate them. Always install the chain that comes with each new certificate.
  • “An expired intermediate only affects old certificates.” Every certificate below it fails, however new.

Why it matters to the business

  • Leader

Check yourself

Check yourself

Chrome shows your site without problems, but a partner's Java client fails with PKIX path building failed. Your server sends only its leaf certificate. What is the best fix?

  1. Ask the partner to import your certificate into their trust store
  2. Configure your server to send the intermediate certificate after the leaf
  3. Switch to a wildcard certificate
Show answer

Answer B Configure your server to send the intermediate certificate after the leaf

Check yourself

Which certificate does a TLS server not need to send?

  1. Its leaf certificate
  2. The intermediate that signed the leaf
  3. The root certificate
Show answer

Answer C The root certificate

Check yourself

How does a client find the issuer of a certificate among several candidate certificates?

  1. It matches the issuer name and Authority Key Identifier with a candidate's subject name and Subject Key Identifier
  2. It takes the certificate with the latest end date
  3. It uses the order in which the CA published them
Show answer

Answer A It matches the issuer name and Authority Key Identifier with a candidate's subject name and Subject Key Identifier

Go deeper

In the gameLevel 13: The chainChain Bridge, Keystone. In development

The crash course covers chains in lesson 6, Chains and trust stores.

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

Last reviewed Edition 0.9