CertHaloSSL expiry monitoring · by IncubXperts

Fix an incomplete SSL certificate chain (missing intermediate)

Why a site works in Chrome but fails in curl, apps and APIs, how to confirm a missing intermediate certificate, and how to serve the full chain on any server.

CertHalo teamUpdated 3 min read

Applies toNginxApacheIISNode.jsAWSJavaPython

A missing intermediate certificate is one of the most confusing SSL problems, because the site often looks fine in your browser while it fails everywhere else. This guide explains why, how to confirm it in a minute, and how to fix it on common servers.

The symptom

The website opens normally in Chrome or Edge, but:

  • curl says SSL certificate problem: unable to get local issuer certificate.
  • Python reports CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate.
  • Java fails with PKIX path building failed.
  • Mobile apps, payment providers, webhooks or API clients can't connect.
  • Some Android phones or older devices show a certificate warning.

Why it happens

Certificate authorities don't sign your certificate with their root key directly. They use an intermediate certificate, so the full chain is:

Your certificate → intermediate certificate → root certificate (built into devices)

Devices ship with the roots, but not the intermediates. Your server must send its intermediate together with its own certificate. If it only sends its own, the client can't link it to a trusted root.

Desktop browsers often hide the problem: they cache intermediates they've seen before, or download the missing one using a link inside the certificate. Most other software doesn't, so it fails.

Confirm it in a minute

Use our SSL checker: an incomplete chain shows as "Certificate chain complete and trusted: Fail" with the explanation.

Or with openssl, look at the verification result at the end of the output:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | grep "Verify return code"
  • Verify return code: 0 (ok): the chain is fine.
  • Verify return code: 21 (unable to verify the first certificate): the intermediate is missing.

You can also count how many certificates the server sends; a correct setup sends at least two:

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
  | grep -c "BEGIN CERTIFICATE"

The fix: serve the full chain

You need a full-chain file: your certificate first, followed by the intermediate certificate(s). Don't include the root.

  • Let's Encrypt (Certbot) creates it for you: fullchain.pem.
  • Commercial certificates come with a "CA bundle" or "intermediate" file. Combine them, your certificate first:
cat your_domain.crt ca_bundle.crt > fullchain.crt

Check the combined file before using it (openssl verify should print OK):

openssl verify -untrusted ca_bundle.crt your_domain.crt

Nginx

Point ssl_certificate at the full chain, not at your certificate alone:

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

Then sudo nginx -t && sudo systemctl reload nginx. More in How to install or renew SSL on Nginx.

Apache

On Apache 2.4.8 and later, SSLCertificateFile takes the full chain:

SSLCertificateFile    /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

On older versions, keep your certificate in SSLCertificateFile and add the intermediate with SSLCertificateChainFile. Then sudo apachectl configtest && sudo systemctl reload apache2. More in How to install or renew SSL on Apache.

IIS (Windows)

IIS sends the intermediates it finds in the Windows certificate store. Open Manage computer certificates (certlm.msc), go to Intermediate Certification Authorities → Certificates, right-click All Tasks → Import and import your CA's intermediate certificate. Importing a .pfx that includes the chain also works. More in How to install or renew SSL on IIS.

Node.js

Load the full chain as the certificate:

https.createServer({ key: fs.readFileSync("privkey.pem"), cert: fs.readFileSync("fullchain.pem") }, app);

AWS load balancers and CloudFront

Certificates issued by AWS Certificate Manager always include their chain. For a certificate you import into ACM, paste the intermediate(s) into the Certificate chain field when importing; leaving it empty causes exactly this problem.

After fixing

Run the SSL checker again, or the openssl command above, and confirm Verify return code: 0 (ok). Fix it everywhere the certificate is installed: every server behind a load balancer, and the next renewal too, if you install certificates by hand.

CertHalo alerts you when a certificate stops being trusted, including when a renewal is installed without its intermediate. Monitor 3 certificates free.

Check your certificate now

See when it expires, who issued it, whether the chain is complete and if it's been revoked. Free, no sign-up.

Want a reminder before it expires? Monitor 3 certificates free.

More guides