This site is an archive and is no longer updated.
    P2MediumRunbook

    Certificate Not Trusted Runbook

    Browser showing 'Not Secure' or certificate errors? This runbook helps you diagnose the specific problem and fix it quickly.

    15-30 minutes
    Last Updated: December 2025
    Progress: 0/29 complete0%
    LinkedInX

    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)

    1. Identify the specific error (chain, expired, name mismatch, revoked)
    2. Run diagnostic commands to confirm
    3. Fix based on error type
    4. 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 subjectAltName

    Check 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/null

    Check 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 -issuer

    Create 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 -dates

    5Fix: 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.com

    Troubleshooting

    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.