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
Platforms
  • Linux
  • nginx
  • Apache
  • Kubernetes
Who it is for
Level
Practitioner · 5 steps
On this page
  1. Before you start
  2. Steps
  3. 1. Identify every certificate you have
  4. 2. Find a missing intermediate
  5. 3. Put them in order and build the file
  6. 4. Verify the file offline
  7. 5. Configure the server
  8. Check it worked
  9. If something goes wrong
  10. Why this works
  11. Go deeper

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 certificate and every intermediate 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.

Steps

1. Identify every certificate you have

for f in *.crt; do
  echo "== $f"; openssl x509 -in "$f" -noout -subject -issuer
done
== 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.

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:

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

That address usually serves DER; convert it to PEM as shown in the DER guide. 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).

Build the file with OpenSSL rather than plain cat:

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

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

openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout
openssl verify -show_chain -CAfile root.crt -untrusted fullchain.pem fullchain.pem
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.

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). See How to install a certificate in nginx.
  • Apache 2.4.8 and later: the same file in SSLCertificateFile; SSLCertificateChainFile is deprecated (mod_ssl docs). See How to install a certificate in Apache httpd.
  • 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).
  • IIS does not use a chain file: it builds the chain from the Windows store, so import the intermediate there (IIS guide).

Reload the server after every change.

Check it worked

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

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

 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:

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.

If something goes wrong

  • Error 20 or 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 (Java), CERTIFICATE_VERIFY_FAILED (Python) or 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 explains the linking, Two roads to the same root explains chains with cross-signed certificates, and the file formats appendix explains PEM bundles.

Go deeper

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

Last reviewed Edition 0.9