# How to install an intermediate SSL certificate (full chain)

> Find the right intermediate certificate, build a correctly ordered full chain, install it on Nginx, Apache, IIS, HAProxy, Tomcat or Node.js.

Source: https://ssl.sh/guides/install-intermediate-certificate/
Last updated: 2026-11-10

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:

```bash
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](https://ssl.sh/tools/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:

```bash
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](https://ssl.sh/tools/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:

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

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

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

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

```powershell
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](https://ssl.sh/tools/ssl-checker/) again, or from any terminal:

```bash
curl ssl.sh/example.com
```

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

```bash
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](https://ssl.sh/monitor/) (in development), which flags incomplete chains.

## Troubleshooting

- **curl: (60) SSL certificate problem: unable to get local issuer certificate**: the intermediate is still missing; see [curl 60](https://ssl.sh/errors/curl-60-ssl-certificate-problem/).
- **Java: PKIX path building failed**: see [PKIX path building failed](https://ssl.sh/errors/java-pkix-path-building-failed/).
- **Node.js: UNABLE_TO_VERIFY_LEAF_SIGNATURE**: see [UNABLE_TO_VERIFY_LEAF_SIGNATURE](https://ssl.sh/errors/node-unable-to-verify-leaf-signature/).
- **Firefox: SEC_ERROR_UNKNOWN_ISSUER**: see [SEC_ERROR_UNKNOWN_ISSUER](https://ssl.sh/errors/sec-error-unknown-issuer/).
- **Nginx refuses to start with key values mismatch**: the intermediate was placed before the leaf; see [nginx key values mismatch](https://ssl.sh/errors/nginx-key-values-mismatch/).
