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

> Build a fullchain.pem: find the right intermediates, put them in order after your certificate, verify the file offline and check what the server sends.

Source: https://pkizerotohero.com/guides/build-fullchain/ · Last reviewed: 2026-10-20

How-to guide · Install on a server

# How to build the full chain file your server needs

**In one sentence:**

You will have one PEM file with your certificate followed by its intermediates in the right order, verified offline and proven to work with a strict client.

Task

[Install on a server](https://pkizerotohero.com/guides/#task-install)

Platforms

- Linux
- nginx
- Apache
- Kubernetes

Who it is for

- [IT ops](https://pkizerotohero.com/start/#itops)
- [Developer](https://pkizerotohero.com/start/#developer)

Level

Practitioner · 5 steps

## Before you start

- **OpenSSL** (Linux, macOS with Homebrew, or Git Bash on Windows).
- **Your server certificate** as PEM, and whatever the CA sent with it: an intermediate file, a “CA bundle”, or a `.p7b`.
- **The matching private key** stays where it is; the chain file never contains it.

A server must send its [leaf](https://pkizerotohero.com/handbook/glossary/#leaf-certificate) certificate and every [intermediate](https://pkizerotohero.com/handbook/glossary/#intermediate-certificate) up to (not including) the root. Browsers often repair a missing intermediate on their own; curl on Linux, Java, Python, Node.js and many mobile apps do not, and fail with errors such as [`unable to get local issuer certificate`](https://pkizerotohero.com/errors/curl-unable-to-get-local-issuer-certificate/).

## Steps

### 1. Identify every certificate you have

```bash
for f in *.crt; do
  echo "== $f"; openssl x509 -in "$f" -noout -subject -issuer
done
```

```text
== int.crt
subject=C=BE, O=Keystone Demo, CN=Keystone Demo Issuing CA 1
issuer=C=BE, O=Keystone Demo, CN=Keystone Demo Root CA
== root.crt
subject=C=BE, O=Keystone Demo, CN=Keystone Demo Root CA
issuer=C=BE, O=Keystone Demo, CN=Keystone Demo Root CA
== www.crt
subject=C=BE, O=Example Shop, CN=www.example.com
issuer=C=BE, O=Keystone Demo, CN=Keystone Demo Issuing CA 1
```

The leaf is the one with your host name. A certificate whose subject equals its issuer is a root; leave it out. For a bundle with several certificates in one file, list them with `openssl crl2pkcs7 -nocrl -certfile bundle.pem | openssl pkcs7 -print_certs -noout`. A `.p7b` needs converting first: [How to convert between DER and PEM](https://pkizerotohero.com/guides/convert-der-pem/).

### 2. Find a missing intermediate

If no file has a subject equal to your leaf’s issuer, ask the CA for its current intermediate, or download it from the “CA Issuers” address in your certificate:

```bash
openssl x509 -in www.crt -noout -ext authorityInfoAccess
```

That address usually serves DER; convert it to PEM as shown in [the DER guide](https://pkizerotohero.com/guides/convert-der-pem/). Not every certificate has one: the Baseline Requirements only recommend, and no longer require, the Authority Information Access (AIA) extension in publicly trusted TLS certificates (since 4 October 2026). Use the intermediate that came with **this** certificate. CAs rotate intermediates, and an old chain file with a new leaf fails too.

### 3. Put them in order and build the file

The order is: your certificate, then the certificate that issued it, then the one that issued that, and so on. Stop before the root. The TLS 1.3 specification, RFC 9846, puts it this way: “The sender’s certificate MUST come in the first CertificateEntry in the list”, and the root “MAY be omitted” ([section 4.5.1](https://www.rfc-editor.org/rfc/rfc9846#section-4.5.1)).

Build the file with OpenSSL rather than plain `cat`:

```bash
{ openssl x509 -in www.crt; openssl x509 -in int.crt; } > fullchain.pem
```

Each `openssl x509` writes one clean PEM block ending in a newline, without `Bag Attributes` or other text. Plain `cat www.crt int.crt` works too, unless a file lacks its final newline. Then the boundary breaks:

```text
-----END CERTIFICATE----------BEGIN CERTIFICATE-----
```

OpenSSL-based servers reject that file: OpenSSL itself reports `PEM routines:get_header_and_data:bad end line`, and our Python test server refused it with `SSLError [SSL] PEM lib`.

With two intermediates, add the second after the first: `{ openssl x509 -in www.crt; openssl x509 -in int1.crt; openssl x509 -in int2.crt; } > fullchain.pem`.

### 4. Verify the file offline

```bash
openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout
openssl verify -show_chain -CAfile root.crt -untrusted fullchain.pem fullchain.pem
```

```text
subject=C=BE, O=Example Shop, CN=www.example.com
issuer=C=BE, O=Keystone Demo, CN=Keystone Demo Issuing CA 1

subject=C=BE, O=Keystone Demo, CN=Keystone Demo Issuing CA 1
issuer=C=BE, O=Keystone Demo, CN=Keystone Demo Root CA

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

Each issuer is the next subject, and `OK` means the chain reaches the root. For a certificate from a public CA, leave out `-CAfile`: OpenSSL then uses its own trust store. Also confirm that the key matches the **first** certificate: [How to check that a private key matches a certificate](https://pkizerotohero.com/guides/check-key-matches-certificate/).

### 5. Configure the server

- **nginx:** `ssl_certificate /etc/nginx/tls/fullchain.pem;` and `ssl_certificate_key` for the key. The nginx documentation says: “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#chains)). See [How to install a certificate in nginx](https://pkizerotohero.com/guides/install-certificate-nginx/).
- **Apache 2.4.8 and later:** the same file in `SSLCertificateFile`; `SSLCertificateChainFile` is deprecated ([mod\_ssl docs](https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatefile)). See [How to install a certificate in Apache httpd](https://pkizerotohero.com/guides/install-certificate-apache/).
- **Kubernetes:** `kubectl create secret tls web-tls --cert=fullchain.pem --key=www.key`; the full chain goes in `tls.crt`.
- **certbot** already writes `fullchain.pem` next to `cert.pem`; point your server at `fullchain.pem` ([certbot guide](https://pkizerotohero.com/guides/certbot-lets-encrypt/)).
- **IIS** does not use a chain file: it builds the chain from the Windows store, so import the intermediate there ([IIS guide](https://pkizerotohero.com/guides/install-certificate-iis/)).

Reload the server after every change.

## Check it worked

Ask the live server what it sends, with a strict client:

```bash
openssl s_client -connect www.example.com:443 -servername www.example.com -verify_return_error -showcerts </dev/null 2>/dev/null \
  | grep -E '^ *[0-9]+ s:|^ +i:|Verify return code'
```

Our test server, loading `fullchain.pem` (with `-CAfile root.crt` added, because the test root is private):

```text
 0 s:C = BE, O = Example Shop, CN = www.example.com
   i:C = BE, O = Keystone Demo, CN = Keystone Demo Issuing CA 1
 1 s:C = BE, O = Keystone Demo, CN = Keystone Demo Issuing CA 1
   i:C = BE, O = Keystone Demo, CN = Keystone Demo Root CA
Verify return code: 0 (ok)
```

The same server loading only `www.crt` sent one certificate and failed with `Verify return code: 21 (unable to verify the first certificate)`. Paste the `-showcerts` output into the chain checker for a second opinion:

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

## If something goes wrong

- **Error 20 or [21](https://pkizerotohero.com/errors/openssl-verify-error-21/) from a client:** the intermediate is missing or is the wrong one. Compare the leaf’s issuer with the intermediate’s subject (step 1). Clients say [`PKIX path building failed`](https://pkizerotohero.com/errors/java-pkix-path-building-failed/) (Java), [`CERTIFICATE_VERIFY_FAILED`](https://pkizerotohero.com/errors/python-certificate-verify-failed/) (Python) or [`UNABLE_TO_VERIFY_LEAF_SIGNATURE`](https://pkizerotohero.com/errors/node-unable-to-verify-leaf-signature/) (Node.js).
- **The server will not start, with `key values mismatch`:** the first certificate in the file is not the one that matches the key. Usually the order is reversed. Our test server, given the intermediate first, refused to load the file.
- **`PEM lib` or `bad end line` errors:** a missing newline between two blocks (step 3). Rebuild the file with `openssl x509`.
- **It works in the browser but not in curl:** the browser repaired the chain. Trust the strict test above.
- **The chain worked last year but fails after renewal:** the CA changed intermediates, or an intermediate expired. Use the chain that came with the new certificate.

## Why this works

A client links each certificate to its issuer by name and key identifier and checks each signature, until it reaches a root in its own trust store. It already has the root, so the server must supply everything in between, starting with its own certificate. Strict clients do not download missing links, so the file you configure decides whether they succeed. [Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/) explains the linking, [Two roads to the same root](https://pkizerotohero.com/handbook/cross-signing/) explains chains with cross-signed certificates, and the [file formats appendix](https://pkizerotohero.com/handbook/file-formats/) explains PEM bundles.

## Go deeper

- [RFC 9846, section 4.5.1](https://www.rfc-editor.org/rfc/rfc9846#section-4.5.1): the order of certificates a TLS 1.3 server sends.
- [RFC 5280, section 4.2.2.1](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1): Authority Information Access and the “CA Issuers” address.
- [nginx: SSL certificate chains](https://nginx.org/en/docs/http/configuring_https_servers.html#chains): building and using a chain file.
- [Apache mod\_ssl: `SSLCertificateFile`](https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatefile): one file with the leaf and its intermediates.
- [OpenSSL `verify` manual](https://docs.openssl.org/master/man1/openssl-verify/): `-untrusted`, `-CAfile` and `-show_chain`.

**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=/guides/build-fullchain/).

Last reviewed 20 October 2026Edition 0.9

## Keep going

### Learn why it works

- [Chapter 13: Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/)
- [Chapter 14: Two roads to the same root: cross-signing and root expiry](https://pkizerotohero.com/handbook/cross-signing/)
- [File formats: PEM, DER, PKCS #12 and friends](https://pkizerotohero.com/handbook/file-formats/)

### Error pages

- [SSL certificate problem: unable to get local issuer certificate](https://pkizerotohero.com/errors/curl-unable-to-get-local-issuer-certificate/)
- [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/)
- [UNABLE\_TO\_VERIFY\_LEAF\_SIGNATURE](https://pkizerotohero.com/errors/node-unable-to-verify-leaf-signature/)
- [\[SSL: CERTIFICATE\_VERIFY\_FAILED\] certificate verify failed: unable to get local issuer certificate](https://pkizerotohero.com/errors/python-certificate-verify-failed/)

### Tools

- [Chain checker](https://pkizerotohero.com/tools/chain/): Paste a chain and see which link is missing, expired or out of order.

### Topics

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

## More guides: Install on a server

- [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/)
