Incomplete HTTPS Certificate Chain: Browser Distrusts a “Valid” Leaf
Symptom: decode looks fine, browsers disagree
A common TLS time sink:
- Paste the leaf into a certificate decoder — Not Before / Not After look healthy
- SAN contains your hostname
- Mobile Safari, strict corporate proxies, or default
curlstill report unable to get local issuer certificate or “not trusted”
The leaf is not “fake”. The trust path is broken: the client never receives (or cannot match) the intermediate that signed that leaf.
How many layers matter
For day-to-day debugging, three tiers are enough:
| Tier | Signed by | Trusted by default? |
|---|---|---|
| Leaf (end-entity) | Intermediate CA | No — must be proven by the chain |
| Intermediate | Root (or another intermediate) | Usually not fully preloaded |
| Root | Self-signed | Yes — in the OS/browser trust store |
The server must send: leaf + enough intermediates to reach a trusted root. Do not stuff the root into your nginx ssl_certificate chain in most setups (unnecessary; some clients are picky).
A five-minute triage
1. Pull the chain with SNI
openssl s_client -connect www.example.com:443 \
-servername www.example.com -showcerts </dev/null
Without -servername, a multi-cert IP may return a different certificate and send you on a wild goose chase.
Count BEGIN CERTIFICATE blocks:
- Only one: highly suspicious — leaf-only
- Two or more: save them as
leaf.pem,int1.pem, …
2. Check linkage: Issuer must equal the next Subject
openssl x509 -in leaf.pem -noout -issuer -subject
openssl x509 -in int1.pem -noout -issuer -subject
Expect: leaf Issuer == int1 Subject; int1 Issuer lands on a trusted root (or another intermediate). Mismatch means wrong files or a scrambled bundle.
The on-site decoder can show the same PEM fields — see the certificate decoder guide. It does not build a PKIX path; use local OpenSSL for trust decisions.
3. Verify on your machine
# Trust store path varies by distro; a CA bundle file also works
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt \
-untrusted int1.pem leaf.pem
OK means the path closes under this machine’s trust model. unable to get local issuer almost always means a missing intermediate or a bad -untrusted file.
Three deployment root causes
A. Leaf uploaded, fullchain ignored
ACME clients typically emit:
cert.pem— leaf onlyfullchain.pem— leaf + intermediate(s)
Point nginx / Caddy / most cloud LBs at fullchain. Leaf-only often “works on my Chrome” because desktop browsers cached the intermediate, while mobile and strict clients fail immediately.
B. Wrong concatenation order
Correct order: leaf first, intermediates after. Reversed bundles fail hard on some stacks and flake on others. Issuer/Subject linkage beats eyeballing PEM.
C. Intermediate expired or rotated
“Leaf has 60 days left” does not mean the chain is alive. Intermediate rotations (for example Let’s Encrypt’s past cutovers) broke sites that never touched their leaf. Check every tier’s Not After.
Guomi / dual-certificate note
TLCP uses signature + encryption certificates. Chain completeness still applies; you simply have more files and stricter ordering. See the Guomi TLS nginx guide. For RSA key context on the international stack, see the RSA guide.
CDN and reverse-proxy twists
Fixing files on the origin is not enough if TLS terminates at a CDN or cloud load balancer. Confirm which hop presents the certificate to the browser:
- Browser → CDN edge (what users see)
- CDN → origin (often a different cert, or HTTP on a private link)
openssl s_client against the public hostname checks the edge. If you only updated origin disk and forgot to replace the cert object in the CDN console, decode results on the origin PEM will look perfect while the public site stays broken. Always re-pull with s_client against the hostname users type after every change.
Implementer's note
ToolVault’s certificate tool only parses the PEM you paste. It does not fetch remote certificates and does not perform full chain validation — SSRF/privacy for the former, WebCrypto limits for the latter. Separating “read fields” from “decide trust” avoids fake confidence from online tools. Need a trust verdict: local openssl verify or SSL Labs. Need SAN/dates/Issuer clarity: use the certificate decoder.
Checklist
- Does
s_client -servernameshow at least two certificates? - Does the leaf Issuer equal the first intermediate’s Subject?
- Does the server config point at fullchain, not leaf-only?
- Is every tier’s Not After still in the future?
- After fixing files, did you reload the process that actually terminates TLS (container / reverse proxy / CDN origin)?
Related reading
This article is brought to you by ToolVault. More developer tools at the homepage.
Related Tools
Related Articles
Complete Guide to HTTP API Testing: From Basics to Advanced Debugging Techniques
Master the core skills of HTTP API testing, including GET/POST/PUT/DELETE requests, header configuration, request body formats, authentication methods, and debugging techniques. Boost your development efficiency with an online API testing tool.
HTTP GET vs POST: When to Use Each? Semantics, Safety, and Best Practices
GET vs POST semantics, idempotency, caching, URL length limits, security tradeoffs, REST conventions, and common mistakes. Includes curl examples and a decision table for picking the right method.
Unexpected token '<' is not valid JSON — 5 Reasons Your API Returned HTML Instead
fetch throws SyntaxError: Unexpected token '<' when the response body is HTML, not JSON. Covers the 5 root causes: SPA history fallback returning index.html, auth 302 redirects, gateway 502 error pages, wrong baseURL, and missing res.ok checks — with curl diagnostics.