Web servers

How to install an intermediate certificate and fix an incomplete chain

By · Last verified

To install an intermediate certificate, make the server send your certificate followed by each intermediate in issuing order, without the root. Clients that do not fetch intermediates on their own, such as curl, Java and mobile apps, then connect without errors. The steps apply to any CA and to Nginx, Apache httpd 2.4.8+, HAProxy, Tomcat, Node.js and IIS.

Prerequisites

You need your certificate, its private key and shell access to the server.

  • The server certificate (example.com.crt or cert.pem) and the private key already installed.
  • OpenSSL 1.1.1 or 3.x on a machine with internet access.
  • For IIS, an elevated PowerShell session.

Step 1: Check what the server sends today

List the certificates the server sends. A single entry (depth 0 only) means the intermediate is missing:

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | grep -E '^ *[0-9] s:|^ *i:'

The SSL checker reports the same problem as "chain incomplete".

Step 2: Get the correct intermediate

Use the bundle your CA or ACME client gave you: Certbot's fullchain.pem and chain.pem, acme.sh's fullchain.cer, or the "CA bundle" file from a commercial CA. If you only have the leaf, read its CA Issuers URL and download the issuer:

openssl x509 -in example.com.crt -noout -ext authorityInfoAccess
curl -fsSL -o intermediate.der "http://ca-issuers.example.net/intermediate.crt"
openssl x509 -inform DER -in intermediate.der -out intermediate.pem 2>/dev/null || cp intermediate.der intermediate.pem

Replace the URL with the one printed by the first command. The chain builder does this automatically if you paste the certificate.

Step 3: Build the full chain in the right order

A full-chain file lists the leaf first, then each intermediate, each one issued by the next:

cat example.com.crt intermediate.pem > fullchain.pem
openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout

The output lists subject and issuer pairs; each issuer must equal the next subject. Confirm the chain verifies against the system trust store:

openssl verify -untrusted intermediate.pem example.com.crt

Step 4: Configure the server

Each server reads the chain from its own setting; IIS reads it from the Windows certificate store instead of a file.

Nginx: point ssl_certificate at the full chain.

ssl_certificate     /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/example.com.key;

Apache 2.4.8+: SSLCertificateFile takes the full chain; SSLCertificateChainFile is obsolete.

SSLCertificateFile    /etc/ssl/example.com/fullchain.pem
SSLCertificateKeyFile /etc/ssl/example.com/example.com.key

HAProxy: one PEM with full chain and key, cat fullchain.pem example.com.key > /etc/haproxy/certs/example.com.pem.

Tomcat: certificateChainFile gets only the intermediates (intermediate.pem), not the leaf.

Node.js: cert: fs.readFileSync('fullchain.pem').

IIS: import the intermediate into the machine's intermediate store; IIS then sends it automatically.

Import-Certificate -FilePath C:\certs\intermediate.cer -CertStoreLocation Cert:\LocalMachine\CA

Reload the server (sudo nginx -t && sudo systemctl reload nginx, sudo systemctl reload apache2, and so on).

Verify

A fixed server sends the leaf followed by its intermediates, and the SSL checker no longer reports "chain incomplete". Run the SSL checker again, or from any terminal:

curl ssl.sh/example.com

Locally, curl uses its own trust store and does not fetch intermediates, so it is a strict test:

curl -sS -o /dev/null -w '%{http_code}\n' https://example.com

Automatic renewal

Use the chain your ACME client delivers with every renewal, because a chain file assembled by hand eventually goes stale. CAs rotate intermediates, and Let's Encrypt has announced new hierarchies over the years. Point your server at Certbot's fullchain.pem, acme.sh's --fullchain-file or lego's bundled .crt, and never at a separately saved intermediate. For commercial certificates, download the CA bundle again with each reissue. Since certificates are capped at 200 days (and 47 days from March 15, 2029), automate this with ACME where possible and add the host to ssl.sh monitoring (in development), which flags incomplete chains.

Troubleshooting

FAQ

Should I include the root certificate in the chain?

No. Clients already have trusted roots in their stores, and a root sent by the server is ignored for trust decisions. Including it only adds bytes to every handshake.

Why does the site work in Chrome but fail in curl?

Desktop browsers can fetch missing intermediates from the AIA URL or reuse ones cached from other sites. curl, OpenSSL-based clients, Java and most mobile apps do not, so they report an unknown issuer. See missing intermediate certificate.

My CA sent several intermediates. Which ones do I need?

All certificates between your leaf and a root trusted by your clients, in issuing order. The chain builder works this out from your certificate and shows the correct bundle.

Sources

  1. nginx: Configuring HTTPS servers (SSL certificate chains)
  2. Apache httpd: mod_ssl SSLCertificateFile
  3. OpenSSL: openssl-verify
  4. Let's Encrypt: Chains of Trust