The three causes, ranked by frequency
When a browser or security scanner flags your certificate as untrusted, one of three things has happened. Here they are in order of how often each actually occurs in practice:
- Incomplete intermediate certificate chain. Your domain certificate was issued by an intermediate CA — a certificate authority that itself sits between your cert and a root CA the browser trusts. TLS requires the server to send both the leaf certificate (your domain cert) and any intermediate certificates in the chain. If the server only sends the leaf, the browser cannot complete the chain verification. This is by far the most common cause of the "not trusted" error on properly-issued, non-expired certificates. It is also the most operator-fixable with zero cost.
-
Expired certificate. The
notAfterdate in your certificate has passed. Browsers will refuse an expired certificate outright — the error message usually says "expired" explicitly rather than "not trusted." Automated renewal via Let's Encrypt / Certbot or a cloud provider's managed certificate service prevents this entirely if configured correctly. - Self-signed certificate or private CA root. A self-signed certificate is not issued by any recognised CA — it is its own root. Browsers do not trust it because it is not in their root store. This is common in development, internal tools, and corporate PKI where an internal CA root is not distributed to all clients. On public-facing production sites, a certificate from a publicly trusted CA (Let's Encrypt, DigiCert, Sectigo, etc.) is always the right answer.
Diagnosing an incomplete chain
The fastest check is openssl:
openssl s_client -connect yourdomain.com:443 -showcerts 2>/dev/null | grep -c 'BEGIN CERTIFICATE'
Interpret the result:
- 1 — only the leaf certificate is presented. Chain is incomplete.
- 2 or 3 — the server is sending the leaf plus one or two intermediates. Chain is likely complete (verify with a trust check below).
To confirm the chain validates to a trusted root:
openssl s_client -connect yourdomain.com:443 -verify_return_error 2>&1 | grep 'Verify return code'
Verify return code: 0 (ok) means the chain validates. Any other code means the validation failed — note the error string, which will be specific (e.g. "unable to get local issuer certificate" for a missing intermediate).
If you prefer a web UI, SSL Labs at ssllabs.com/ssltest runs a detailed TLS analysis. Look at the "Certification Paths" section: the label Chain issues: Incomplete confirms a missing intermediate. Similarly, testssl.sh (used by pentes.io's TLS check) flags this as a "Chain of trust" warning in its output.
Browsers sometimes hide the problem. Desktop Chrome and Firefox use the AIA (Authority Information Access) extension, defined in RFC 5280 §4.2.2.1, to fetch missing intermediates on the fly. This means a user on a modern desktop browser may see a green padlock even when the server's chain is incomplete. Mobile clients, non-browser TLS clients (API consumers, curl, monitoring agents, IoT devices), and clients behind proxies that block external fetches do not do this — they fail with "not trusted." The chain being incomplete is always wrong, even if some clients mask it.
Fixing an incomplete chain
The fix in all cases is the same: send the full chain. Your certificate authority provides the intermediate certificate(s) in the download bundle they give you when you purchase or issue the certificate. The file is usually labelled something like ca_bundle.crt, intermediate.crt, or chain.pem.
Assemble the full chain PEM in the correct order: leaf certificate first, then each intermediate, each intermediate that signed the previous one, up to (but not including) the root CA. The root is in the browser's trust store and should not be sent by the server.
nginx
ssl_certificate /etc/ssl/certs/yourdomain.fullchain.pem; # leaf + intermediates
ssl_certificate_key /etc/ssl/private/yourdomain.key;
Do not set ssl_certificate to a file containing only the leaf cert. Reload nginx after the change: nginx -t && systemctl reload nginx.
Apache (2.4.8+)
SSLCertificateFile /etc/ssl/certs/yourdomain.fullchain.pem
SSLCertificateKeyFile /etc/ssl/private/yourdomain.key
Apache 2.4.8 and later accepts the chain concatenated into the SSLCertificateFile. On older Apache versions use SSLCertificateChainFile separately.
Let's Encrypt / Certbot
If you are using Certbot, the fix is to ensure you reference fullchain.pem in your web server config, not cert.pem. Certbot creates both — cert.pem is just the leaf; fullchain.pem is leaf plus intermediates. The default Certbot installer configures fullchain.pem correctly, but manual configurations sometimes reference the wrong file.
# Correct (fullchain — includes intermediates):
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
# Wrong (cert only — no intermediates):
ssl_certificate /etc/letsencrypt/live/yourdomain.com/cert.pem;
Other certificate authorities
Every public CA provides an intermediate download. Check their documentation under terms like "installation guide," "CA bundle," or "certificate chain." Mozilla's SSL Configuration Generator (available at ssl-config.mozilla.org) provides tested, copy-paste nginx and Apache configurations that include correct chain handling — useful as a baseline even if you are not using Mozilla's own CA.
Why AIA fetch is not enough (the mobile problem)
RFC 5280 §4.2.2.1 defines the Authority Information Access extension, which lets certificate issuers embed a URL in the certificate where clients can download the missing intermediate. Desktop Chrome and Firefox implement this as a fallback — when the chain is incomplete, they make an outbound HTTP request to fetch the intermediate, complete the chain in memory, and show a green padlock without telling you anything is wrong.
This creates a dangerous false signal. Your site looks fine in a desktop browser test but fails silently for:
- iOS Safari and most mobile browsers (stricter about fetching missing intermediates)
- API clients using
curl, Pythonrequests, Java'sHttpClient, and similar - Corporate environments where a proxy or firewall blocks the CA's AIA URL
- Monitoring and uptime agents that do strict chain validation
- Any scanner that does not implement AIA fetching — including testssl.sh
Sending the full chain from the server is the correct and reliable solution. AIA is a recovery mechanism, not a deployment strategy.
After fixing — verify
After updating your server config and reloading, re-run the openssl check or use SSL Labs to confirm the chain is now complete. Look for Verify return code: 0 (ok) and the absence of "Chain issues" in the SSL Labs report. A pentes.io scan will also pick this up in the TLS section of the next report.
See all security guides in the pentes.io guides index.