# Certificate chains explained: leaf, intermediate and root

> How clients build and verify a chain from your leaf certificate to a trusted root, why missing intermediates break, and what your server should send.

Source: https://ssl.sh/learn/certificate-chains/
Last updated: 2026-11-10

A certificate chain is the list of certificates that connects your server's certificate to a root certificate the client already trusts. The client verifies that each certificate was signed by the one above it and that the chain ends at a trust anchor in its own store. Most production chain failures have one cause: the server omits the intermediate certificate.

## The three kinds of certificates in a chain

Every publicly trusted TLS chain has three roles: the leaf you install, one or more intermediates that sign it, and a root that the client trusts on its own. They are all X.509 certificates in the same format; what differs is who holds the private key, what the certificate is allowed to sign, and whether your server should send it.

| Role | Holder of the private key | Key extension | Typical lifetime | Sent by your server? |
|---|---|---|---|---|
| Leaf (end-entity) | You, on the web server | `basicConstraints: CA:FALSE`, EKU `serverAuth` | Max 200 days since March 15, 2026, falling to 47 days in 2029 | Yes, first |
| Intermediate (subordinate CA) | The CA, in an online HSM | `CA:TRUE`, often `pathlen:0`, keyUsage `keyCertSign` | A few years | Yes, after the leaf |
| Root (trust anchor) | The CA, kept offline | Self-signed, `CA:TRUE` | 15 to 25 years | No, it ships with the OS or browser |

Intermediates exist because roots are too valuable to use every day. The CA/Browser Forum Baseline Requirements forbid a root key from signing subscriber certificates directly; the root signs a handful of intermediates and then goes back into an offline vault. If an intermediate is compromised or misbehaves, the CA or the browsers can distrust that one intermediate without touching the root that is embedded in billions of devices.

```text
   Client trust store
   +-------------------------------+
   | Root CA (self-signed)         |  <- trust anchor, never sent
   +---------------+---------------+
                   | signs
   +---------------v---------------+
   | Intermediate CA               |  <- sent by the server (2nd)
   +---------------+---------------+
                   | signs
   +---------------v---------------+
   | example.com (leaf)            |  <- sent by the server (1st)
   +-------------------------------+
```

The maximum leaf lifetime comes from CA/Browser Forum Ballot SC-081v3: 398 days before March 15, 2026, 200 days from that date, 100 days from March 15, 2027 and 47 days from March 15, 2029. See [the 47-day certificate schedule](https://ssl.sh/learn/47-day-certificates/) for what that means for renewals.

## How a client verifies each link

For every pair of adjacent certificates the client runs the same set of checks, and a single failure anywhere rejects the whole chain. RFC 5280 section 6 defines the path validation algorithm that browsers and TLS libraries implement, with some local policy layered on top.

1. **Signature.** The issuer's public key must verify the signature over the child certificate's `tbsCertificate` (the signed part of the certificate).
2. **Name chaining.** The child's Issuer field must equal the parent's Subject field. When present, the child's Authority Key Identifier (AKI) should match the parent's Subject Key Identifier (SKI).
3. **Validity period.** The current time must fall between `notBefore` and `notAfter` for the leaf and every intermediate.
4. **CA permissions.** Every issuer must have `basicConstraints CA:TRUE` and a keyUsage that includes `keyCertSign`. A `pathLenConstraint` limits how many more CA certificates may appear below it.
5. **Purpose.** The leaf needs the `serverAuth` extended key usage; browsers also treat an EKU in an intermediate as a restriction on everything below it.
6. **Name constraints and policies.** If an intermediate carries name constraints, every name in the leaf must fit inside them.
7. **Hostname.** The hostname the client connected to must match a DNS name in the leaf's [Subject Alternative Name](https://ssl.sh/glossary/san/) extension. The Common Name is ignored by modern clients.
8. **Revocation.** Depending on the client this may be a CRL, OCSP, Chrome's CRLSets or Firefox's CRLite. See [OCSP and CRL](https://ssl.sh/learn/ocsp-and-crl/).

The root's own self-signature is not what makes it trustworthy. A root is trusted because a root program (Chrome, Mozilla, Apple, Microsoft) reviewed the CA and put that certificate in its store. That is why sending a root from your server achieves nothing: a client that already trusts it does not need your copy, and a client that does not trust it will not start trusting it because you sent it.

## Path building: finding the chain is harder than checking it

The server sends a list of certificates, but the client builds the actual path itself and can choose a different path than the one you had in mind. RFC 4158 describes this process; in practice the client starts at the leaf, looks for candidate issuers, and keeps going until it reaches a trust anchor or runs out of candidates.

Candidate issuers come from four places:

- the certificates the server sent in the TLS handshake;
- certificates the client has cached from earlier connections;
- the client's trust store;
- a download from the URL in the leaf's Authority Information Access (AIA) `caIssuers` field, if the client supports AIA fetching.

Matching uses the issuer name and the key identifiers. You can see both in any certificate:

```bash
openssl x509 -in leaf.pem -noout -issuer -ext authorityKeyIdentifier,authorityInfoAccess
openssl x509 -in intermediate.pem -noout -subject -ext subjectKeyIdentifier
```

```text
issuer=O=Example, CN=Example Intermediate CA
X509v3 Authority Key Identifier:
    90:D2:53:AB:0B:EC:3E:74:FE:C3:EE:EC:24:F5:3A:54:15:B6:99:F6
Authority Information Access:
    CA Issuers - URI:http://ca.example.com/int.der
subject=O=Example, CN=Example Intermediate CA
X509v3 Subject Key Identifier:
    90:D2:53:AB:0B:EC:3E:74:FE:C3:EE:EC:24:F5:3A:54:15:B6:99:F6
```

The leaf's AKI equals the intermediate's SKI, and the issuer name equals the intermediate's subject, so the client knows which certificate to try next.

### Cross-signing creates more than one valid path

A cross-signed certificate gives one CA two issuers, so the same leaf can chain to two different roots. The CA gets a second certificate for the same name and the same public key, signed by another root. Because the key is identical, anything the CA signs verifies against either certificate.

Let's Encrypt's current hierarchy is a good example. As of November 2026, its Chains of Trust page lists the Generation Y intermediates YE1 and YE2 (ECDSA P-384) and YR1 and YR2 (RSA 2048) in active issuance, under two new roots, Root YE and Root YR, which had not yet been accepted into root stores. To make them work today, the new roots are cross-signed by the existing ISRG roots:

```text
ECDSA certificates (default chain first)
  example.com <- YE1 <- Root YE <- ISRG Root X2 <- ISRG Root X1
  example.com <- YE1 <- Root YE <- ISRG Root X2
  example.com <- YE1 <- Root YE

RSA certificates
  example.com <- YR1 <- Root YR <- ISRG Root X1
  example.com <- YR1 <- Root YR
```

A client that only trusts ISRG Root X1 needs the longest path. A client that trusts ISRG Root X2 can stop one step earlier. Once Root YE is included in a trust store, clients using that store can stop at Root YE and ignore the rest. "Root YE" appears twice in this story: as a self-signed root in future trust stores, and as a cross-signed certificate that your server sends as an intermediate today.

### When path building goes wrong: the 2021 DST Root CA X3 expiry

The Let's Encrypt root transition in 2021 shows what happens when clients build paths differently. Let's Encrypt originally chained to IdenTrust's DST Root CA X3, which expired on September 30, 2021. To keep old Android devices working, Let's Encrypt served a chain in which ISRG Root X1 was cross-signed by DST Root CA X3.

- Clients that prefer trusted certificates (OpenSSL 1.1.0 and later do this by default) stopped at the self-signed ISRG Root X1 in their store and never looked at the expired DST root.
- OpenSSL 1.0.2 followed the served chain up to the expired DST Root CA X3 and failed, even though a valid path existed.
- Android versions before 7.1.1 did not trust ISRG Root X1 but also did not check the expiry of a trust anchor, so the cross-sign kept them working for three more years.

That cross-sign expired on September 30, 2024. Let's Encrypt said dropping it cut the certificate bytes sent in a TLS handshake by over 40%, which is a reminder that every extra certificate in the chain has a cost.

## The missing intermediate problem

A server that sends only its leaf certificate works in some browsers and fails almost everywhere else, which makes it the most confusing chain bug to diagnose. The leaf alone cannot be linked to a root, so the client either finds the intermediate some other way or gives up.

| Client | What happens when the intermediate is missing |
|---|---|
| Chrome and Edge on desktop | Usually download it from the AIA caIssuers URL; the page loads |
| Firefox | Fills gaps from its preloaded list of known intermediates; often loads |
| curl, wget, OpenSSL `s_client` | Fail with "unable to get local issuer certificate" |
| Python (`requests`, `urllib`) | Fail with `CERTIFICATE_VERIFY_FAILED` |
| Java | Fails with "PKIX path building failed" (AIA fetching is off by default) |
| Node.js | Fails with `UNABLE_TO_VERIFY_LEAF_SIGNATURE` |
| Go | Fails with "x509: certificate signed by unknown authority" |
| Mobile apps, IoT devices, payment terminals, webhooks | Usually fail |

Because the developer testing in a desktop browser sees a working site, the bug tends to surface first in monitoring, API clients or customer reports. Specific fixes are in [missing intermediate certificate](https://ssl.sh/errors/missing-intermediate-certificate/), [curl error 60](https://ssl.sh/errors/curl-60-ssl-certificate-problem/), [Python CERTIFICATE_VERIFY_FAILED](https://ssl.sh/errors/python-certificate-verify-failed/), [Java PKIX path building failed](https://ssl.sh/errors/java-pkix-path-building-failed/) and [Node UNABLE_TO_VERIFY_LEAF_SIGNATURE](https://ssl.sh/errors/node-unable-to-verify-leaf-signature/).

## What your server should send

Send the leaf first, then each intermediate in signing order, and leave the root out. TLS 1.2 (RFC 5246) required this exact order. TLS 1.3 (RFC 8446 section 4.4.2) only requires the server's own certificate to come first and asks clients to cope with extra or unordered certificates, but keeping the strict order costs nothing and avoids surprises with older clients.

Most ACME clients write the right file for you. Certbot, for example, produces `cert.pem` (leaf only), `chain.pem` (intermediates only) and `fullchain.pem` (leaf plus intermediates). Point your web server at `fullchain.pem`:

```nginx
server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}
```

For Apache 2.4.8 and later, put the leaf and intermediates in the file named by `SSLCertificateFile`; the older `SSLCertificateChainFile` directive is deprecated. HAProxy expects one PEM file containing the leaf, the intermediates and the private key.

A few rules avoid most trouble:

- **Do not hard-code an intermediate.** Let's Encrypt states that it may start issuing from a backup intermediate at any time without warning. Always deploy the chain that came with the certificate.
- **Do not pin intermediates** in apps or API clients. [Certificate pinning](https://ssl.sh/glossary/certificate-pinning/) against an intermediate breaks the day the CA rotates it.
- **Match the key type.** ECDSA leaves come from ECDSA intermediates and RSA leaves from RSA intermediates; mixing up chain files after switching key types produces a chain that does not verify.
- **Remove stale certificates.** An expired intermediate left in the bundle can send strict clients down a dead path.

## Inspecting and verifying a chain with OpenSSL

Two commands answer most chain questions: one shows exactly what the server sends, the other verifies a chain the way a strict client would. First, list every certificate in the handshake:

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

Each certificate appears with `s:` (subject) and `i:` (issuer) lines. Certificate 0 is the leaf; its issuer must be the subject of certificate 1, and so on. If you see only certificate 0, the intermediate is missing.

Then verify files locally. With only the root as a trust anchor and no intermediate, verification fails exactly like curl does:

```bash
openssl verify -CAfile root.pem leaf.pem
```

```text
CN=example.com
error 20 at 0 depth lookup: unable to get local issuer certificate
error leaf.pem: verification failed
```

Supplying the intermediate as an untrusted certificate lets OpenSSL build the full path, and `-show_chain` prints it:

```bash
openssl verify -show_chain -CAfile root.pem -untrusted intermediate.pem leaf.pem
```

```text
leaf.pem: OK
Chain:
depth=0: CN=example.com (untrusted)
depth=1: O=Example, CN=Example Intermediate CA (untrusted)
depth=2: O=Example Root, CN=Example Root CA
```

"Untrusted" here only means the certificate was not a trust anchor; it was still checked. More recipes, including verifying against the system store and checking a remote host, are in the [chain verification cookbook](https://ssl.sh/cookbook/verify-chains/). The [SSL checker](https://ssl.sh/tools/ssl-checker/) and [chain builder](https://ssl.sh/tools/chain-builder/) do the same checks in the browser and can rebuild a correct chain from a leaf.

## Chains, formats and size

A chain is several certificates in a row, and the file format decides how they are stored. In PEM you concatenate the blocks; in PKCS#7 (`.p7b`) the certificates sit in one container without a key; in PKCS#12 (`.pfx`) the chain travels together with the private key. [Certificate formats](https://ssl.sh/learn/certificate-formats/) explains when you need each one.

Chain length also has a performance cost. Every certificate adds roughly one to two kilobytes to the handshake before any page data flows, which matters on mobile networks and for QUIC, where the server may send only a limited amount of data before the client's address is validated. That is one reason CAs keep chains short and why a long cross-signed path, like the one Let's Encrypt uses during its Generation Y transition, is meant to be temporary.
