<!-- https://pkizerotohero.com/handbook/certificate-chains/ | Markdown mirror of the HTML page | PKI Zero to Hero, by Duyutech BV -->

> How certificate chains work: leaf, intermediate and root, AKI and SKI, AIA fetching, why curl and Java fail on a missing intermediate, and the fix.

Source: https://pkizerotohero.com/handbook/certificate-chains/ · Last reviewed: 2026-10-20

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

- [Developer](https://pkizerotohero.com/start/#developer)
- [IT ops](https://pkizerotohero.com/start/#itops)
- [Expert](https://pkizerotohero.com/start/#expert)
- [Beginner](https://pkizerotohero.com/start/#beginner)
- [Leader](https://pkizerotohero.com/start/#leader)

Level: **Practitioner**

- [New to this? Start with The idea](https://pkizerotohero.com/handbook/certificate-chains/#the-idea)
- [Developer? Jump to How it really works](https://pkizerotohero.com/handbook/certificate-chains/#how-it-really-works)
- [IT ops? At work](https://pkizerotohero.com/handbook/certificate-chains/#at-work)
- [Leader? Why it matters](https://pkizerotohero.com/handbook/certificate-chains/#why-it-matters-to-the-business)

## 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](https://pkizerotohero.com/handbook/glossary/#certificate-chain):

1. the **leaf**: your server’s certificate, for `www.example.com`;
2. one or more [intermediate certificates](https://pkizerotohero.com/handbook/glossary/#intermediate-certificate): CAs that sign leaf certificates day to day;
3. the **root**: the CA certificate that sits in your trust store ([chapter 12](https://pkizerotohero.com/handbook/trust-stores/)).

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”.

In the game

In the game: the Chain Bridge

In Level 13, Ken links Rosa’s certificate to the Notary’s office and the office to the royal root, plank by plank, across the Chain Bridge. Visitors’ browsers cross even when a plank is missing, because they carry spare planks. Carts, the apps and tools, fall in.

Where the analogy stops being true: a bridge holds because of its first stone, but a chain of certificates holds because of its last link. The root is trusted because it is already in the client’s trust store, not because it signed itself. And every link is checked separately: its signature, its dates and its permissions.

## How it really works

- Developer
- Expert

### How does a client link the certificates?

For each certificate, the client looks for the certificate of its issuer. Two fields connect them ([RFC 5280](https://www.rfc-editor.org/rfc/rfc5280)):

- **Names**: the leaf’s `issuer` must equal the intermediate’s `subject`.
- **Key identifiers**: the leaf’s [Authority Key Identifier](https://pkizerotohero.com/handbook/glossary/#authority-key-identifier) (AKI) usually equals the intermediate’s [Subject Key Identifier](https://pkizerotohero.com/handbook/glossary/#subject-key-identifier) (SKI) ([sections 4.2.1.1 and 4.2.1.2](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.1)). This helps when one CA has several keys with the same name.

Our test chain from [chapter 8](https://pkizerotohero.com/handbook/x509-certificates/) 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](https://pkizerotohero.com/handbook/cross-signing/)), good clients try alternatives ([RFC 4158](https://www.rfc-editor.org/rfc/rfc4158) describes the techniques).

*Diagram: Three boxes stacked from bottom to top. The leaf www.example.com points up to the intermediate Keystone Demo Issuing CA 1, with the label: issuer name and AKI match the subject and SKI above. The intermediate points up to the root Keystone Demo Root CA, which sits inside a box labelled trust store. Each step is marked with a tick for signature valid, and the root with a tick for found in trust store.*

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](https://www.rfc-editor.org/rfc/rfc5280#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](https://pkizerotohero.com/handbook/revocation/));
- **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](https://pkizerotohero.com/handbook/names-and-key-usage/)). 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](https://www.rfc-editor.org/rfc/rfc9846#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](https://pkizerotohero.com/handbook/glossary/#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](https://www.rfc-editor.org/rfc/rfc5280#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](https://chromium.googlesource.com/chromium/src/+/main/net/cert/cert_verify_proc_builtin.cc)), and Apple’s platform verifier, used by Safari, fetches missing intermediates by default for TLS ([Apple developer documentation](https://developer.apple.com/documentation/security/sectrustsetnetworkfetchallowed%28_%3A_%3A%29)). The Windows certificate chain engine behind many Windows apps downloads missing issuers too, and .NET does so unless an application sets [`DisableCertificateDownloads`](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509chainpolicy.disablecertificatedownloads).
- **Firefox** does not fetch AIA. Instead, it [preloads the intermediates](https://blog.mozilla.org/security/2020/11/13/preloading-intermediate-ca-certificates-into-firefox/) 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`](https://pkizerotohero.com/errors/firefox-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.

*Diagram: A server sends only its leaf certificate, with no intermediate. Three clients receive it. A browser downloads the intermediate from the AIA address or uses a cached copy and reaches the root: works, marked with a tick. curl, Python and Node.js, built on OpenSSL, cannot find the issuer: fails with unable to get local issuer certificate, marked with a cross. Java fails with PKIX path building failed, marked with a cross.*

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:

```bash
openssl s_client -connect letsencrypt.org:443 -servername letsencrypt.org -showcerts </dev/null
```

```text
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](https://pkizerotohero.com/handbook/cross-signing/) 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:

```bash
openssl s_client -connect 127.0.0.1:8443 -servername www.example.com -CAfile root.crt </dev/null
```

```text
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](https://pkizerotohero.com/errors/openssl-verify-error-20/), it cannot find the leaf’s issuer, and [error 21](https://pkizerotohero.com/errors/openssl-verify-error-21/), so it cannot verify even the first certificate. Other clients say the same thing differently:

```text
# 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`](https://pkizerotohero.com/errors/curl-unable-to-get-local-issuer-certificate/), and Java prints [`PKIX path building failed`](https://pkizerotohero.com/errors/java-pkix-path-building-failed/). The error pages for [Python](https://pkizerotohero.com/errors/python-certificate-verify-failed/) and [Node.js](https://pkizerotohero.com/errors/node-unable-to-verify-leaf-signature/) 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:

```text
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`:

```bash
openssl verify -CAfile root.crt www.crt
openssl verify -show_chain -CAfile root.crt -untrusted int.crt www.crt
```

```text
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](https://nginx.org/en/docs/http/configuring_https_servers.html)):

```bash
cat www.example.com.crt intermediate.crt > www.example.com.fullchain.crt
```

```text
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](https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatefile)).

**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](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/iis/www-authentication-authorization/configure-intermediate-certificates)):

```powershell
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:

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

### Check yours

*Interactive tool: Chain checker (runs in the browser at [/tools/chain/](https://pkizerotohero.com/tools/chain/))*

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

Why it matters to the business

The outage your browser test will not show

A server that does not send its intermediate certificate looks fine in a browser, yet breaks mobile apps, partner integrations, payment callbacks and scripts. These are often the most valuable connections, and they fail silently on the other side. The fix costs minutes, the diagnosis can take days. Make sure monitoring checks your endpoints with a strict client rather than a browser, and that renewal installs the full chain automatically, including when the CA changes its intermediates.

## 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

What happened

Send the full chain from your server: the leaf, then the intermediate.

Why

Chrome can download or reuse the missing intermediate, but Java does not by default, so it cannot link your leaf to a trusted root.

The rule

The server must send its intermediates. Fix the server, not the clients.

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

What happened

The root can be left out.

Why

A client trusts a root only if it already has it in its trust store, so sending it adds no trust.

The rule

Send the leaf first, then the intermediates. Leave out the root.

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

What happened

It matches names and key identifiers, then checks the signature.

Why

The issuer field must equal the issuer certificate's subject, and the AKI points to the issuer's SKI. The signature then proves the link.

The rule

Chains link by name and key identifier, and every link is verified by its signature.

## Go deeper

- [RFC 5280, section 6](https://www.rfc-editor.org/rfc/rfc5280#section-6): the path validation algorithm, step by step.
- [RFC 9846, section 4.5.1](https://www.rfc-editor.org/rfc/rfc9846#section-4.5.1): what a TLS 1.3 server must put in its Certificate message.
- [RFC 4158](https://www.rfc-editor.org/rfc/rfc4158): how clients build paths when there are several candidates.
- [Mozilla: Preloading intermediate CA certificates into Firefox](https://blog.mozilla.org/security/2020/11/13/preloading-intermediate-ca-certificates-into-firefox/): why Firefox does not need AIA fetching.
- [nginx: SSL certificate chains](https://nginx.org/en/docs/http/configuring_https_servers.html) and [Apache `SSLCertificateFile`](https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatefile): configuring the full chain.
- [Microsoft: configure intermediate certificates for IIS](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/iis/www-authentication-authorization/configure-intermediate-certificates): how IIS chooses the chain it sends.
- [OpenSSL `s_client`](https://docs.openssl.org/master/man1/openssl-s_client/) and [`verify`](https://docs.openssl.org/master/man1/openssl-verify/): inspecting and verifying chains.

The crash course covers chains in [lesson 6, Chains and trust stores](https://pkizerotohero.com/course/chains-and-trust-stores/).

**Early edition.** Checked against the standards; an independent expert review is in progress. Found a mistake? [Tell us](https://pkizerotohero.com/contact/?topic=content-error&page=/handbook/certificate-chains/).

Last reviewed 20 October 2026Edition 0.9

## Keep going

Crash course: [Lesson 6: Chains and trust stores](https://pkizerotohero.com/course/chains-and-trust-stores/)

### Error pages for this chapter

- [SSL certificate problem: unable to get local issuer certificate](https://pkizerotohero.com/errors/curl-unable-to-get-local-issuer-certificate/)
- [verify error:num=20:unable to get local issuer certificate](https://pkizerotohero.com/errors/openssl-verify-error-20/)
- [verify error:num=21:unable to verify the first certificate](https://pkizerotohero.com/errors/openssl-verify-error-21/)
- [PKIX path building failed … unable to find valid certification path to requested target](https://pkizerotohero.com/errors/java-pkix-path-building-failed/)
- [\[SSL: CERTIFICATE\_VERIFY\_FAILED\] certificate verify failed: unable to get local issuer certificate](https://pkizerotohero.com/errors/python-certificate-verify-failed/)
- [UNABLE\_TO\_VERIFY\_LEAF\_SIGNATURE](https://pkizerotohero.com/errors/node-unable-to-verify-leaf-signature/)
- [SEC\_ERROR\_UNKNOWN\_ISSUER](https://pkizerotohero.com/errors/firefox-sec-error-unknown-issuer/)

### How-to guides

- [How to check the certificate chain a server sends](https://pkizerotohero.com/guides/check-remote-certificate-chain/)
- [How to create a CSR on Windows (certreq and IIS)](https://pkizerotohero.com/guides/create-csr-windows/)
- [How to convert PFX (PKCS #12) to PEM and back](https://pkizerotohero.com/guides/convert-pfx-pem/)
- [How to build the full chain file your server needs](https://pkizerotohero.com/guides/build-fullchain/)
- [How to work behind a TLS-inspecting corporate proxy, safely](https://pkizerotohero.com/guides/corporate-proxy-certificates/)
- [How to trust a private root CA on Windows](https://pkizerotohero.com/guides/add-root-ca-windows/)
- [How to trust a private root CA on macOS](https://pkizerotohero.com/guides/add-root-ca-macos/)
- [How to trust a private root CA on Linux](https://pkizerotohero.com/guides/add-root-ca-linux/)
- [How to add a CA to a Java trust store (cacerts)](https://pkizerotohero.com/guides/add-root-ca-java/)
- [How to make Python, Node.js and npm trust a private CA](https://pkizerotohero.com/guides/trust-ca-python-node/)
- [How to install a certificate in nginx](https://pkizerotohero.com/guides/install-certificate-nginx/)
- [How to install a certificate in Apache httpd](https://pkizerotohero.com/guides/install-certificate-apache/)
- [How to install a certificate in IIS](https://pkizerotohero.com/guides/install-certificate-iis/)
- [How to install a certificate in Tomcat and other Java servers](https://pkizerotohero.com/guides/install-certificate-tomcat/)
- [How to build a test CA with OpenSSL (lab only)](https://pkizerotohero.com/guides/openssl-test-ca/)

### Tools

- [Chain checker](https://pkizerotohero.com/tools/chain/): Paste a chain and see which link is missing, expired or out of order.
- [Certificate decoder](https://pkizerotohero.com/tools/decoder/): Paste a PEM certificate and read every field in plain words.

### Topics

- [Chains and trust stores](https://pkizerotohero.com/topics/chains-and-trust/)
