Certificates

Certificate chains explained: leaf, intermediate and root

By · Last verified

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.

   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 for what that means for renewals.

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

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:

openssl x509 -in leaf.pem -noout -issuer -ext authorityKeyIdentifier,authorityInfoAccess
openssl x509 -in intermediate.pem -noout -subject -ext subjectKeyIdentifier
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:

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, curl error 60, Python CERTIFICATE_VERIFY_FAILED, Java PKIX path building failed and 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:

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

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:

openssl verify -CAfile root.pem leaf.pem
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:

openssl verify -show_chain -CAfile root.pem -untrusted intermediate.pem leaf.pem
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. The SSL checker and 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 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.

FAQ

Should I include the root certificate in the chain my server sends?

No. A root is trusted only because the client already has it in its trust store, so sending it adds bytes without changing the result. The one exception is a cross-signed certificate for a newer root (for example Let's Encrypt's Root YE signed by ISRG Root X2): in that path it acts as an intermediate and must be sent.

In what order should the certificates be in fullchain.pem?

Leaf first, then the intermediate that signed the leaf, then the certificate that signed that intermediate, and so on up to (but not including) the root. TLS 1.3 only strictly requires the leaf to come first, but many TLS 1.2 clients and older libraries expect the full order.

Why does my site work in Chrome but curl says 'unable to get local issuer certificate'?

Your server is almost certainly sending only the leaf certificate. Chrome on desktop downloads the missing intermediate through the AIA caIssuers URL and hides the problem; curl and OpenSSL never do that. Install the full chain (leaf plus intermediates) and test again with openssl s_client -showcerts.

What is a cross-signed certificate?

It is a second certificate for the same CA name and public key, signed by a different, usually older and more widely trusted, root. Because both certificates carry the same key, anything the CA signs can chain to either issuer, which lets a new root become usable before it is in every trust store.

Sources

  1. RFC 5280: Internet X.509 PKI Certificate and CRL Profile (section 6, path validation)
  2. RFC 4158: Certification Path Building
  3. RFC 8446: TLS 1.3 (section 4.4.2, Certificate message)
  4. Let's Encrypt: Chains of Trust
  5. Let's Encrypt: Shortening the Let's Encrypt Chain of Trust (2023)
  6. Let's Encrypt: DST Root CA X3 Expiration (September 2021)
  7. OpenSSL 3.0 manual: openssl-verify