Certificate Not Trusted Runbook
Browser showing 'Not Secure' or certificate errors? This runbook helps you diagnose the specific problem and fix it quickly.
When to Use
- • Browser shows "Your connection is not private"
- • Users reporting certificate warnings
- • ERR_CERT errors in browser
- • API clients failing with SSL verification errors
Do NOT Use For
- • Certificate is expired (use Emergency Replacement Runbook)
- • Known key compromise (use Key Compromise Response Runbook)
Quick Reference (TL;DR)
- Identify the specific error (chain, expired, name mismatch, revoked)
- Run diagnostic commands to confirm
- Fix based on error type
- Verify from external sources
1Identify the Error
Objective: Determine exactly what type of trust error you're dealing with
💡 ERR_CERT_AUTHORITY_INVALID → Chain/trust problem
💡 ERR_CERT_DATE_INVALID → Expired or not yet valid
💡 ERR_CERT_COMMON_NAME_INVALID → Wrong domain name
💡 NET::ERR_CERT_REVOKED → Certificate was revoked
💡 SSL_ERROR_RX_RECORD_TOO_LONG → Not HTTPS or wrong port
2Quick Diagnosis
Objective: Run commands to understand what the server is sending
View certificate being served:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -subject -issuer -dates -ext subjectAltNameCheck chain completeness:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
grep -E "(depth|verify|Certificate chain)"Full chain dump:
echo | openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/nullCheck from curl:
curl -vI https://example.com 2>&1 | grep -E "(SSL|certificate|subject|issuer|expire)"3Fix: Chain Incomplete
Objective: Add missing intermediate certificates to complete the chain
Find the issuer of your certificate:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -issuerCreate proper bundle (order matters):
# Order: Your cert first, then intermediates (root optional)
cat your-cert.crt intermediate.crt > bundle.crt
# For nginx:
ssl_certificate /path/to/bundle.crt;
ssl_certificate_key /path/to/private.key;Reload after update:
# nginx
sudo nginx -t && sudo systemctl reload nginx
# Apache
sudo apachectl configtest && sudo systemctl reload apache2💡 Download intermediates from your CA's repository page
💡 Common intermediate download pages: DigiCert, Sectigo, Let's Encrypt
💡 Use our Chain Builder demo to help construct the correct chain
4Fix: Certificate Expired
Objective: Replace the expired certificate
Check expiration:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -dates5Fix: Wrong Domain Name
Objective: Ensure certificate covers the correct domains
View CN and SANs:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -subject -ext subjectAltName💡 Wildcard *.example.com does NOT cover example.com (apex)
💡 Wildcard only covers one level: *.example.com covers www.example.com but not a.b.example.com
💡 Check if accessing via IP - certificates don't cover IPs unless explicitly in SAN
6Fix: Certificate Revoked
Objective: Replace the revoked certificate
Check revocation status via OCSP:
# Get OCSP responder URL from certificate
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -ocsp_uri
# Query OCSP (requires issuer cert)
openssl ocsp -issuer issuer.crt -cert server.crt \
-url http://ocsp.digicert.com -resp_text💡 Revoked certificates CANNOT be un-revoked - must get new certificate
💡 If compromised: generate NEW key, don't reuse old key
7Fix: Self-Signed Certificate
Objective: Replace with trusted CA-issued certificate
Check if self-signed:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -subject -issuer
# If Subject and Issuer are identical = self-signed💡 For internal use only: distribute root cert to clients instead of replacing
💡 For public facing: must use trusted CA (Let's Encrypt is free)
8Verify the Fix
Objective: Confirm the trust issue is resolved
Verify trust from command line:
# Should show: Verify return code: 0 (ok)
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
grep "Verify return code"Test with curl (will fail if untrusted):
curl -I https://example.comTroubleshooting
Problem: Fixed chain but some clients still fail
Solution: Some clients cache the old chain. Wait for cache to expire or test from new client/device.
Problem: Browser trusts it but curl doesn't
Solution: Curl uses system trust store which may differ from browser. Update CA certificates: sudo update-ca-certificates
Problem: Only fails on older devices/browsers
Solution: May need to include cross-signed intermediate for legacy compatibility. Check CA documentation.
Problem: Works from browser but fails from code/API
Solution: Application may be using own trust store. Check language/framework documentation for adding CA certs.