Home/Server & Website Managed Operations/Troubleshooting Incomplete Certificate Chains: OpenSSL Errors, Verify Codes, and fullchain Fixes
Server & Website Managed Operations

Troubleshooting Incomplete Certificate Chains: OpenSSL Errors, Verify Codes, and fullchain Fixes

The certificate is clearly installed, yet the browser warns of insecurity; more commonly and棘手ly, another scenario occurs—it works perfectly on your end, but clients cannot access it.

This latter issue almost always stems from one cause: an incomplete certificate chain. The reason for this discrepancy—working locally but failing remotely—is that different clients adopt entirely different strategies when encountering missing intermediate certificates.

Why “It Works for Me, But Clients Can’t Access It”

A trusted certificate relies not on itself, but on a chain of trust behind it:

Root Certificate (Trusted by Browser/OS)
  └── Intermediate Certificate
        └── Your Domain Certificate

During an HTTPS handshake, the server should theoretically send both the “domain certificate + intermediate certificate.” If it sends only the domain certificate, the client lacks a link in the chain and cannot connect to its trusted root.

In reality, most browsers support a mechanism called AIA (Authority Information Access) completion: if the chain is broken, they download the missing intermediate certificate from the URL recorded in the certificate’s Authority Information Access field. Thus, Chrome appears normal.

However, Android, Java, curl, openssl, and some mini-program environments do not perform this completion. If the chain is broken, it remains broken, resulting in direct errors.

This explains why, at the same minute, “you can access it while clients cannot.” It also explains why such issues often drag on—because operations teams cannot reproduce them locally.

Error Messages from Various Clients

Browsers

Chrome / Edge: NET::ERR_CERT_AUTHORITY_INVALID
Chrome / Edge: ERR_CERT_COMMON_NAME_INVALID
Firefox:       SEC_ERROR_UNKNOWN_ISSUER
iOS Safari:    Cannot verify server identity
Android:       Security certificate problem with this website

Java Applications (Common in Mini-programs and API Calls)

sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target

OpenSSL Diagnostics

When the chain is incomplete, the output of openssl s_client looks like this—pay attention to the critical Verify return code line:

depth=0 CN = example.com
verify error:num=20:unable to get local issuer certificate
verify return:1
---
Certificate chain
 0 s:CN = example.com
   i:C = US, O = (CA Name), CN = R3
---
Verify return code: 21 (unable to verify the first certificate)

When the chain is complete, this line should read:

Verify return code: 0 (ok)

Verify Return Code Reference Table

This number is the diagnostic result; no guessing needed:

code Meaning Root Cause
21 unable to verify the first certificate Only domain certificate configured, missing intermediate (most common)
20 unable to get local issuer certificate Same as above, or client trust store lacks root
19 self signed certificate in certificate chain Self-signed certificate mixed into the chain
18 self signed certificate Test self-signed certificate used in production
10 certificate has expired Certificate expired
9 certificate is not yet valid Certificate not yet active, usually due to incorrect server time

Troubleshooting Commands

# ① View full chain —务必 include -servername (SNI), otherwise you test the default site cert
openssl s_client -connect example.com:443 -servername example.com -showcerts

# ② View only the diagnostic conclusion line
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null 
  | grep -E 'Verify return code|verify error'

# ③ Offline verification of local certificate files
openssl verify -untrusted fullchain.pem cert.pem

# ④ Check if certificate and private key match — three hashes must be identical
openssl x509 -noout -modulus -in cert.pem    | openssl md5
openssl rsa  -noout -modulus -in privkey.pem | openssl md5
openssl req  -noout -modulus -in request.csr | openssl md5

# ⑤ View Issuer, validity period, SAN
openssl x509 -in cert.pem -noout -text | grep -E 'Issuer:|Subject:|Not Before|Not After'
openssl x509 -in cert.pem -noout -ext subjectAltName

# ⑥ Simulate a strict client that does NOT do AIA completion (equivalent to Android/Java behavior)
openssl s_client -connect example.com:443 -servername example.com -verify_return_error

Item ⑥ is particularly useful—it allows you to reproduce the failure seen by clients locally, without needing to find an old Android phone.

Fix: Wrong Certificate File Used

Using common auto-issuance tools as an example, four files are typically generated after signing, but many people are unsure which to use:

File Content Used by Nginx?
cert.pem Domain certificate only ❌ Using alone results in incomplete chain
chain.pem Intermediate certificate ❌
fullchain.pem cert.pem + chain.pem ✅ Must use this
privkey.pem Private key ✅ Configure for ssl_certificate_key

Changing ssl_certificate from cert.pem to fullchain.pem solves 90% of these issues.

Correct Nginx Configuration

server {
    listen 443 ssl http2;
    server_name example.com;

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

    ssl_protocols              TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers  off;
    ssl_session_cache          shared:SSL:10m;
}

Manually Concatenating fullchain

# Order is fixed: Domain certificate first, then intermediate
# Root certificates generally do not need to be included in the chain
cat cert.pem intermediate.pem > fullchain.pem

# Validate before reloading (not restarting)
nginx -t && systemctl reload nginx

Decision Tree

Client reports certificate untrusted
└─ openssl s_client -servername domain → Check Verify return code
   ├─ 21 / 20  → Incomplete chain    → Change ssl_certificate to fullchain.pem
   ├─ 19 / 18  → Mixed self-signed   → Remove self-signed, keep domain + intermediate
   ├─ 10       → Expired             → Renew
   ├─ 9        → Not yet valid       → Sync server time (ntpdate / chronyd)
   └─ 0 (ok) but still erroring
      ├─ ERR_CERT_COMMON_NAME_INVALID → SAN missing domain, reissue with SAN
      ├─ Only Android/Java error      → Old OS does not trust new root
      └─ Only specific regions error  → CDN nodes not synced with certificate

Certificate Chains Degrade Over Time: Upstream Trust Chain Changes

The previous points covered “misconfiguration.” Another, more troublesome category exists: You didn’t change any configuration, yet the chain broke on its own.

In the certificate system, roots and cross-signatures have expiration dates. When upstream adjusts the trust chain or a cross-signature expires, older devices relying on it will suddenly start reporting certificate warnings—while these devices’ systems have long stopped updating, their trust stores won’t automatically add new roots.

These issues have several characteristics; recognizing them saves time:

  • Not caused by your changes: Server config hasn’t changed, yet the fault occurs, so checking change logs is useless.
  • Impact varies by device: New devices work, old devices fail. This directly invalidates the “it works for me” verification method.
  • Compatibility exceptions are special cases, not rules: Some platforms may have specific compatible cross-signatures continuing to work, but this applies only to that platform and cannot be generalized to other clients.
  • Client OpenSSL version affects results: Older versions (1.0.x) have known flaws in certificate validation. Even if the client trusts the new root and the server chain is complete, it may still fail.

The last point is worth remembering separately because it breaks a common assumption:

Even if “Server chain is correct” + “Client trusts root certificate” are both true, failure can still occur.

If you confirm the chain is complete and the root is in the trust store, but still see error 21, check the client’s OpenSSL version—the problem might not be on your side.

The conclusion is simple: Certificate expiration and trust chains must be part of daily monitoring, not just checked when problems arise. Upstream changes won’t notify you in advance, but the faults will be counted against you.

Scenario Walkthrough: PC Browser Normal, Android Reports Untrusted Certificate

Phenomenon: The site certificate was recently replaced. Developers accessing via desktop browsers repeatedly see no issues. At the same time, some Android devices report “Certificate Untrusted.” Switching networks, clearing caches, or reinstalling browsers doesn’t help.

  1. Reproduce via browser first → Desktop side is completely normal, address bar shows a green lock.
    The purpose here is to confirm “the site isn’t missing a certificate” and note an anomaly: same URL, opposite conclusions for different clients.
  2. Realize browsers auto-complete, switch to CLI immediately → Browsers fetch missing intermediates automatically,
    so their “normality” is not evidence of a complete chain. Use strict validation where auto-completion fails.
  3. Strict validation returns non-zero, indicating inability to get local issuer → The chain is indeed broken by one level.
  4. Count how many certificates the server actually sent → Only 1, meaning the config file attached the site certificate itself, without merging the intermediate.
  5. Confirm intermediate source → Extract intermediate from the cert package, merge with site cert in order to form a complete chain, reload service.
  6. Re-verify with strict validation and check mobile → Return code becomes 0, sent certificates increase to 2+, previously erroring Android devices recover.
# Key is -verify_return_error: it prevents OpenSSL from auto-completing like a browser
echo | openssl s_client -connect example.com:443 -servername example.com 
  -verify_return_error 2>&1 | grep -E 'Verify return code|depth'

# Count how many certs server sent (1 = missing intermediate)
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null 
  | grep -c 'BEGIN CERTIFICATE'

# Full chain order matters: Site cert first, then intermediate
cat site.crt intermediate.crt > fullchain.pem
nginx -t && systemctl reload nginx

Root Cause: The config file attached the site certificate itself rather than the full chain; intermediates were not sent.
Desktop browsers auto-completed, masking the issue, while mobile clients did not, leading to two different outcomes for the same config.

Two points worth remembering:

  • “Browser normal” does not equal “Chain complete”; there is no logical deduction between them.
    Only CLI with strict validation determines completeness; the single parameter -verify_return_error determines if the conclusion is reliable.
  • Certificate package concatenation order matters. Site cert first, intermediate second; reversing causes load failure or incomplete chain;
    Do not manually edit file content; replacing the whole package is safest.

8 Common Pitfalls

  1. Using cert.pem instead of fullchain.pem: Browsers don’t show issues, but Android/Java/mini-programs fail directly. #1 cause of this fault type.
  2. Concatenation order reversed: Must be domain cert first, intermediate second; reversing causes partial client validation failures.
  3. Putting root cert in the chain: Redundant and lengthens the chain; generally only place domain cert + intermediate.
  4. Forgetting -servername during testing: With multiple HTTPS sites on one machine (SNI), omitting this tests the default site cert, yielding completely wrong conclusions.
  5. Directly using restart after changes: Cuts off ongoing requests; correct approach is nginx -t && reload, and -t must pass first.
  6. Certificate and private key mismatch: Nginx starts with key values mismatch; common after renewal if only cert was swapped, not key.
  7. Old systems don’t trust new roots: Recently issued roots may not exist in trust stores of low-version Android (7.1 and below), causing widespread failure on these devices; requires server-side compatible chain config.
  8. CDN scenarios require config on both sides: If CDN has cert but origin doesn’t (or vice versa), some nodes work while others error.

Real Customer Feedback (From Cloud Market Reviews and Consultations for Similar Products)

The following excerpts are taken from real buyer reviews and consultations for similar operations services on Alibaba Cloud / Tencent Cloud / Huawei Cloud marketplaces (service providers include our shop and peers, provided for reference only, not fictional tickets):

10***68 2024-08-10 5.0 Secure & Reliable | Affordable | Skilled Tech Good to use 31***29 2024-07-05 5.0 Skilled Tech | Affordable | Secure & Reliable Glory’s Brave Brother 14:52:36 I stayed up all night last night and couldn’t fix it. Haha Glory’s Brave Brother 14:52:42 Indeed, specialists do specialized tasks Glory’s Brave Brother 14:52:46 Amazingly good 31***29 2024-07-05 5.0 Skilled Tech | Affordable | Secure & Reliable Glory’s Brave Brother 14:52:36 I stayed up all night last night and couldn’t fix it. Haha Glory’s Brave Brother 14:52:42 Indeed, specialists do specialized tasks Glory’s Brave Brother 14:52:46 Amazingly good 10***31 2022-12-07 5.0 Patient & Professional | Affordable | On-time Delivery Good to use

10***31 2022-12-08 5.0 Comprehensive Features | Affordable | Skilled Tech Configured quickly.

10***65 2024-01-04 5.0 On-time Delivery | Secure & Reliable (User did not fill in comment content) 10***26 2023-06-19 5.0 Affordable | Comprehensive Features | Skilled Tech Professional SSL config merchant on cloud market 10***18 2023-06-16 5.0 On-time Delivery | Comprehensive Features | Affordable Configured quickly 10***13 2023-06-16 5.0 Perfect After-sales | Timely Reply | Patient & Professional Good to use 10***31 2023-01-09 5.0 Timely Reply | Skilled Tech | Affordable Deployed for a long time myself without success, found the guy to handle it quickly

FREE CHECKUP

Free server checkup

We will send a problem checklist and remediation advice within one business day — no obligation to engage.

135 410 16684
Weekdays Mon – Fri 10:00 – 22:00 (UTC+8) · 7×24 emergency response
Call now
CONTACT

Contact Us

How can we help?

* Required