SSL certificate chain checker.

Works in Chrome, fails in curl? This tool connects once, captures the certificate chain exactly as your server sends it, and shows which clients will accept it and which will refuse: browsers, curl, Python, Java, Node, Go, Android. If an intermediate is missing it fetches it the way a browser would, tells you who will not, and hands you the corrected fullchain.pem.

Connected to 104.154.89.105:443 as expired.badssl.com · 3 certificates served JSON · text

Verdict All 13 clients refuse this certificate 0 pass · 0 with a caveat · 13 fail
Chain Complete Every intermediate is served
Hostname Matches expired.badssl.com
Leaf expires Expired 2015-04-12T23:59:59Z

Who will this break? modelled from the served chain

VerdictClientWhy
Browsers
fail Chrome (desktop and Android) The leaf certificate is outside its validity period.NET::ERR_CERT_DATE_INVALID
fail Firefox The leaf certificate is outside its validity period.SEC_ERROR_EXPIRED_CERTIFICATE
fail Safari (macOS and iOS) The leaf certificate is outside its validity period.errSSLCertExpired
fail Windows (Edge, .NET, PowerShell) The leaf certificate is outside its validity period.CERT_E_EXPIRED
Command line
fail curl and wget (OpenSSL) The leaf certificate is outside its validity period.SSL certificate problem: certificate has expired
fail openssl s_client The leaf certificate is outside its validity period.verify error:num=10:certificate has expired
Languages
fail Python (requests, urllib3, certifi) The leaf certificate is outside its validity period.[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired
fail Node.js The leaf certificate is outside its validity period.CERT_HAS_EXPIRED
fail Go (net/http) The leaf certificate is outside its validity period.x509: certificate has expired or is not yet valid
fail Java (JDK 11, 17, 21) The leaf certificate is outside its validity period.PKIX path validation failed: CertificateExpiredException
fail Ruby, PHP and Perl (OpenSSL) The leaf certificate is outside its validity period.certificate verify failed (certificate has expired)
Mobile and devices
fail Android apps (7.0 and later) The leaf certificate is outside its validity period.java.security.cert.CertPathValidatorException: Certificate expired
fail Docker and Alpine containers The leaf certificate is outside its validity period.certificate has expired

* passes, but for a reason the server did not earn. Verdicts come from the served chain, the Mozilla root store, 121 roots, as of 13 August 2026, and how each client is known to behave; nothing is executed in a browser or a JVM. Client root stores differ at the edges (see the reference table below).

Findings

  • fail
    Leaf certificate expired 2015-04-12

    Every client refuses an expired certificate. Renew and deploy; if a renewal already ran, it did not reach this endpoint.

  • pass
    Chain completeness 2 intermediates served

    The server sends every intermediate a strict client needs. The chain builds to a root in the Mozilla store without fetching anything.

  • fail
    Intermediate expiry expired 2020-05-30

    COMODO RSA Certification Authority (COMODO CA Limited) has expired. Every leaf signed by it fails, however fresh the leaf is. Your CA has a newer intermediate; download the current bundle and redeploy, or reissue.

  • pass
    Chain order leaf first

    Leaf, then each issuer in turn.

  • pass
    Hostname expired.badssl.com

    The certificate covers this name.

What the server sent in the order it sent it

  1. leaf

    *.badssl.com

    expired
    Issuer
    COMODO RSA Domain Validation Secure Server CA (COMODO CA Limited)
    Valid
    2015-04-09T00:00:00Z to 2015-04-12T23:59:59Z
    Key
    RSA 2048 bit
    Signature
    SHA256-RSA
    SHA-256
    BA105CE02BAC76888ECEE47CD4EB7941653E9AC993B61B2EB3DCC82014D21B4F
    SANs
    • *.badssl.com
    • badssl.com
  2. intermediate

    COMODO RSA Domain Validation Secure Server CA (COMODO CA Limited)

    872d left
    Issuer
    COMODO RSA Certification Authority (COMODO CA Limited)
    Valid
    2014-02-12T00:00:00Z to 2029-02-11T23:59:59Z
    Key
    RSA 2048 bit
    Signature
    SHA384-RSA
    SHA-256
    02AB57E4E67A0CB48DD2FF34830E8AC40F4476FB08CA6BE3F5CD846F646840F0
  3. intermediate

    COMODO RSA Certification Authority (COMODO CA Limited)

    expired
    Issuer
    AddTrust External CA Root (AddTrust AB)
    Valid
    2000-05-30T10:48:38Z to 2020-05-30T10:48:38Z
    Key
    RSA 4096 bit
    Signature
    SHA384-RSA
    SHA-256
    4F32D5DC00F715250ABCC486511E37F501A899DEB3BF7EA8ADBBD3AEF1C412DA

Path a browser builds: *.badssl.com › COMODO RSA Domain Validation Secure Server CA (COMODO CA Limited) › COMODO RSA Certification Authority (COMODO CA Limited)

The fix nothing to change in the chain

The chain is complete and in order. If a client still fails against this host, the problem is on the client: an old root store (Node, certifi or Java that has not been updated), a container without ca-certificates, or a corporate proxy re-signing traffic. How to tell which.

fullchain.pem
-----BEGIN CERTIFICATE-----
MIIFSzCCBDOgAwIBAgIQSueVSfqavj8QDxekeOFpCTANBgkqhkiG9w0BAQsFADCB
kDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxNjA0BgNV
BAMTLUNPTU9ETyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBD
QTAeFw0xNTA0MDkwMDAwMDBaFw0xNTA0MTIyMzU5NTlaMFkxITAfBgNVBAsTGERv
bWFpbiBDb250cm9sIFZhbGlkYXRlZDEdMBsGA1UECxMUUG9zaXRpdmVTU0wgV2ls
ZGNhcmQxFTATBgNVBAMUDCouYmFkc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAMIE7PiM7gTCs9hQ1XBYzJMY61yoaEmwIrX5lZ6xKyx2PmzA
S2BMTOqytMAPgLaw+XLJhgL5XEFdEyt/ccRLvOmULlA3pmccYYz2QULFRtMWhyef
dOsKnRFSJiFzbIRMeVXk0WvoBj1IFVKtsyjbqv9u/2CVSndrOfEk0TG23U3AxPxT
uW1CrbV8/q71FdIzSOciccfCFHpsKOo3St/qbLVytH5aohbcabFXRNsKEqveww9H
dFxBIuGa+RuT5q0iBikusbpJHAwnnqP7i/dAcgCskgjZjFeEU4EFy+b+a1SYQCeF
xxC7c3DvaRhBB0VVfPlkPz0sw6l865MaTIbRyoUCAwEAAaOCAdUwggHRMB8GA1Ud
IwQYMBaAFJCvajqUWgvYkOoSVnPfQ7Q6KNrnMB0GA1UdDgQWBBSd7sF7gQs6R2lx
GH0RN5O8pRs/+zAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUE
FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwTwYDVR0gBEgwRjA6BgsrBgEEAbIxAQIC
BzArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzAI
BgZngQwBAgEwVAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5jb21vZG9jYS5j
b20vQ09NT0RPUlNBRG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNybDCB
hQYIKwYBBQUHAQEEeTB3ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LmNvbW9kb2Nh
LmNvbS9DT01PRE9SU0FEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0
MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wIwYDVR0RBBww
GoIMKi5iYWRzc2wuY29tggpiYWRzc2wuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBq
evHa/wMHcnjFZqFPRkMOXxQhjHUa6zbgH6QQFezaMyV8O7UKxwE4PSf9WNnM6i1p
OXy+l+8L1gtY54x/v7NMHfO3kICmNnwUW+wHLQI+G1tjWxWrAPofOxkt3+IjEBEH
fnJ/4r+3ABuYLyw/zoWaJ4wQIghBK4o+gk783SHGVnRwpDTysUCeK1iiWQ8dSO/r
ET7BSp68ZVVtxqPv1dSWzfGuJ/ekVxQ8lEEFeouhN0fX9X3c+s5vMaKwjOrMEpsi
8TRwz311SotoKQwe6Zaoz7ASH1wq7mcvf71z81oBIgxw+s1F73hczg36TuHvzmWf
RwxPuzZEaFZcVlmtqoq8
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
+AZxAeKCINT+b72x
-----END CERTIFICATE-----

Reproduce what this page saw, from any machine with OpenSSL:

openssl s_client -connect expired.badssl.com:443 -servername expired.badssl.com -showcerts </dev/null 2>/dev/null | grep -c 'BEGIN CERT'
# 1 = leaf only, 2 or more = leaf plus intermediates

curl -vI https://expired.badssl.com 2>&1 | grep -iE 'issuer|SSL certificate problem'

Server configuration for the file above is in how to build a correct fullchain.pem.

About this tool

This server opens one TLS connection to the host and port you give it, with the SNI name you give (the host by default), and keeps the certificates exactly as the server sent them, in the order it sent them. It then does what a strict client does: tries to build a path from the leaf to a root in the Mozilla root store, 121 roots, as of 13 August 2026 using only what was served. If that fails it does what Chrome does instead: follows the Authority Information Access URL in the leaf to fetch the missing issuer, and tries again. The difference between those two results is the whole "works in Chrome, fails in curl" problem, and the verdict matrix is that difference spelled out per client.

The verdicts are modelled, not executed. No browser, JVM or Python interpreter runs on this server. Each row is a documented behaviour: whether the client fetches intermediates, whether it has public intermediates preloaded, whether it requires a Subject Alternative Name, and what it prints when it fails. Root stores differ slightly between clients (Java's cacerts, Node's compiled-in bundle and Android's store all lag Mozilla by months to years), so a chain that ends in a very new root can fail on one of them and pass here; the table below says where to look.

What the chain checker reports

CheckWhy it matters
Chain completenessWhether the served certificates alone reach a trusted root. If they do not, and fetching the issuer over AIA makes them, the chain is incomplete: browsers recover, most other clients fail with unable to get local issuer certificate. This is the most common real-world certificate fault.
Client verdictsPass, pass with a caveat, or fail for 13 client families, each with the reason and the exact error string that client prints, so you can match it to the one in your logs.
Missing intermediatesThe certificate the server should be sending and is not, fetched from the CA so you can see it and so the fullchain below is complete.
Order and extrasWhether the leaf comes first and each issuer follows, whether the root is being sent (wasted bytes), and whether a certificate in the bundle signs nothing (usually a leftover from a previous CA).
ExpiryOf the leaf and of every intermediate. An expired intermediate takes every site signed by it down at once, and monitoring that watches only the leaf will not see it coming.
Hostname and SANWhether the certificate covers the name you asked for, and whether it has a Subject Alternative Name at all. CN-only certificates are refused by Chrome, Firefox, Safari, Go and Python 3.
Signature and keySHA-1 signatures and RSA under 2048 bits, both rejected by current clients.
The fixA fullchain.pem built from the correct path, leaf first, root omitted, ready to paste, plus the OpenSSL command to see what this page saw.

The clients in the matrix

Each row of the verdict table is one of these behaviours. "Fetches" means the client follows the AIA URL in a leaf to download a missing intermediate. "Preloads" means it ships every publicly disclosed intermediate and so rarely needs to. The error column is what you will find in that client's logs when the chain is incomplete.

ClientFetchesError on a missing intermediateNotes
Chrome (desktop and Android)yesNET::ERR_CERT_AUTHORITY_INVALIDFetches missing intermediates from the AIA URL in the leaf, and caches ones it has seen. Uses the Chrome Root Store, which tracks Mozilla's closely.
FirefoxpreloadsSEC_ERROR_UNKNOWN_ISSUERDoes not fetch over AIA, but preloads every intermediate disclosed to Mozilla's CA programme, so a missing public intermediate is usually papered over.
Safari (macOS and iOS)yesThis Connection Is Not Private (errSSLXCertChainInvalid)Apple's verifier fetches missing intermediates. iOS 13 and later also require a SAN and refuse leaves valid for more than 825 days.
Windows (Edge, .NET, PowerShell)yesCERT_E_CHAINING / PartialChainSchannel fetches missing intermediates and downloads roots on demand from Windows Update, so it is the most forgiving client here.
curl and wget (OpenSSL)noSSL certificate problem: unable to get local issuer certificateReads the distribution's ca-certificates bundle (Mozilla's roots) and never fetches intermediates. This is the client most people discover the problem with.
openssl s_clientnoverify error:num=20:unable to get local issuer certificateThe reference view of what came over the wire. -showcerts prints exactly the certificates the server sent.
Python (requests, urllib3, certifi)no[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificaterequests uses the certifi bundle, which is Mozilla's roots as of whenever certifi was last upgraded. No AIA fetching. Python 3.7 and later ignore the CN entirely.
Node.jsnoUNABLE_TO_VERIFY_LEAF_SIGNATURE: unable to verify the first certificateCompiles Mozilla's roots into the binary rather than reading the system store, so an old Node release is an old root store. No AIA fetching.
Go (net/http)nox509: certificate signed by unknown authorityUses the system store on Linux (so an Alpine image needs ca-certificates installed). No AIA fetching. Refuses CN-only certificates since Go 1.15.
Java (JDK 11, 17, 21)noPKIX path building failed: unable to find valid certification path to requested targetUses the JDK's own cacerts file, not the operating system's. AIA fetching exists but is off unless com.sun.security.enableAIAcaIssuers=true is set.
Ruby, PHP and Perl (OpenSSL)nocertificate verify failed (unable to get local issuer certificate)All three wrap OpenSSL and behave like curl: system roots, no AIA.
Android apps (7.0 and later)nojavax.net.ssl.SSLHandshakeException: Trust anchor for certification path not foundConscrypt never fetches intermediates, and apps do not share Chrome's cache. Android 7.1.1 and older additionally lack ISRG Root X1 (Let's Encrypt).
Docker and Alpine containersnounable to get local issuer certificateWhatever runs inside behaves like curl or Go above. If the image has no ca-certificates package at all, every HTTPS connection fails whatever the server sends; that one is fixed in the Dockerfile, not on the server.

Why a certificate works in Chrome but fails in curl

A public certificate is not trusted on its own. The client has to build a path from your leaf, through one or more intermediates, to a root it already has. The server is supposed to send the leaf and every intermediate; the client supplies the root. When the server sends only the leaf, a strict client has a leaf signed by something it has never seen, and stops there.

Browsers are not strict. Chrome, Safari and Windows read the AIA URL embedded in the leaf and download the missing intermediate on the fly. Firefox has every public intermediate preloaded. All of them also cache intermediates from other sites, and Let's Encrypt's are on half the web. So the person who deployed the certificate opens the site, sees a padlock, and ships. Then a webhook sender, a mobile app, a CI job or a monitoring agent connects, and every one of them fails, because none of them fetch. The longer version is why your certificate works in Chrome but fails in curl.

How to build a correct fullchain.pem

Every server wants the same thing: the leaf first, then each intermediate in turn, in one PEM file, and no root. The result panel above produces exactly that from the correct path. Certbot writes it as fullchain.pem next to cert.pem; the mistake is pointing the server at cert.pem. Commercial CAs send it as a separate ca-bundle.crt or intermediate.crt; the mistake is not concatenating it. The step-by-step version, including how to find the right intermediate when the CA's download page is unhelpful, is how to build a correct fullchain.pem.

# nginx
ssl_certificate     /etc/ssl/site/fullchain.pem;
ssl_certificate_key /etc/ssl/site/privkey.pem;

# Apache 2.4.8 and later (SSLCertificateChainFile is deprecated)
SSLCertificateFile    /etc/ssl/site/fullchain.pem
SSLCertificateKeyFile /etc/ssl/site/privkey.pem

# HAProxy wants the key in the same file
cat fullchain.pem privkey.pem > /etc/haproxy/certs/site.pem
bind :443 ssl crt /etc/haproxy/certs/site.pem

# Check the file before you reload: prints one subject/issuer pair per certificate
openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout

Cloud load balancers (AWS ALB and CloudFront, Google, Azure Front Door) have a separate "certificate chain" field when you upload your own certificate. It is optional in the form and mandatory in practice: leave it empty and you have shipped a leaf-only chain to every non-browser client.

Common chain problems

Examples

From the command line

curl "kirkdiamond.com/tools/chain?host=example.com" gives the verdict matrix and findings as plain text, with the corrected fullchain appended when the served one is incomplete; add &format=json for structured output including fullchain_pem, and &port= and &sni= as needed. Only public hosts can be checked, the AIA fetch goes through the same guard, and nothing about the connection is stored.