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 incomplete-chain.badssl.com · 1 certificate served JSON · text

Verdict 9 of 13 clients refuse this chain 3 pass · 1 with a caveat · 9 fail
Chain Incomplete 2 intermediates missing, recovered over AIA
Hostname Matches incomplete-chain.badssl.com
Leaf expires 57 days 2026-12-28T20:02:55Z

Who will this break? modelled from the served chain

VerdictClientWhy
Browsers
pass Chrome (desktop and Android) Fetches the missing YR1 (Let's Encrypt) and Root YR (ISRG) over AIA and carries on. This is why it works in a browser.
pass* Firefox Does not fetch, but has YR1 (Let's Encrypt) and Root YR (ISRG) preloaded, so it passes for now. Nothing the server did earned that.
pass Safari (macOS and iOS) Fetches the missing YR1 (Let's Encrypt) and Root YR (ISRG) over AIA and carries on. This is why it works in a browser.
pass Windows (Edge, .NET, PowerShell) Fetches the missing YR1 (Let's Encrypt) and Root YR (ISRG) over AIA and carries on. This is why it works in a browser.
Command line
fail curl and wget (OpenSSL) Does not fetch missing intermediates. The server must send YR1 (Let's Encrypt) and Root YR (ISRG).SSL certificate problem: unable to get local issuer certificate
fail openssl s_client Does not fetch missing intermediates. The server must send YR1 (Let's Encrypt) and Root YR (ISRG).verify error:num=20:unable to get local issuer certificate
Languages
fail Python (requests, urllib3, certifi) Does not fetch missing intermediates. The server must send YR1 (Let's Encrypt) and Root YR (ISRG).[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
fail Node.js Does not fetch missing intermediates. The server must send YR1 (Let's Encrypt) and Root YR (ISRG).UNABLE_TO_VERIFY_LEAF_SIGNATURE: unable to verify the first certificate
fail Go (net/http) Does not fetch missing intermediates. The server must send YR1 (Let's Encrypt) and Root YR (ISRG).x509: certificate signed by unknown authority
fail Java (JDK 11, 17, 21) Does not fetch missing intermediates. The server must send YR1 (Let's Encrypt) and Root YR (ISRG).PKIX path building failed: unable to find valid certification path to requested target
fail Ruby, PHP and Perl (OpenSSL) Does not fetch missing intermediates. The server must send YR1 (Let's Encrypt) and Root YR (ISRG).certificate verify failed (unable to get local issuer certificate)
Mobile and devices
fail Android apps (7.0 and later) Does not fetch missing intermediates. The server must send YR1 (Let's Encrypt) and Root YR (ISRG).javax.net.ssl.SSLHandshakeException: Trust anchor for certification path not found
fail Docker and Alpine containers Does not fetch missing intermediates. The server must send YR1 (Let's Encrypt) and Root YR (ISRG).unable to get local issuer certificate

* 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

  • pass
    Leaf certificate 57 days left

    Within its validity period.

  • fail
    Chain completeness incomplete

    The server does not send YR1 (Let's Encrypt) and Root YR (ISRG). Chrome, Safari and Windows fetch it from http://yr1.i.lencr.org/ and carry on; curl, Python, Java, Go, Node and Android do not, and fail with "unable to get local issuer certificate" or their equivalent. Serve the fullchain below.

    Why this matters: unable to get local issuer certificate: what it means and how to fix it

  • pass
    Hostname incomplete-chain.badssl.com

    The certificate covers this name.

What the server sent in the order it sent it

  1. leaf

    *.badssl.com

    57d left
    Issuer
    YR1 (Let's Encrypt)
    Valid
    2026-09-29T20:02:56Z to 2026-12-28T20:02:55Z
    Key
    RSA 2048 bit
    Signature
    SHA256-RSA
    SHA-256
    101154813E8FDAB6BA4FAB575185C1A13944BCEC1D849AE2CD94A073494997E3
    SANs
    • *.badssl.com
    • badssl.com
  2. missing

    YR1 (Let's Encrypt)

    not served fetched over AIA
    Issuer
    Root YR (ISRG)
    Valid
    2025-09-03T00:00:00Z to 2028-09-02T23:59:59Z
    Fetched from
    http://yr1.i.lencr.org/
    SHA-256
    13949634D99CD6FD6AA80BC034FEFACCEB1969FEEF986586713ECDBB05758D3F
  3. missing

    Root YR (ISRG)

    not served fetched over AIA
    Issuer
    ISRG Root X1 (Internet Security Research Group)
    Valid
    2026-05-13T00:00:00Z to 2032-09-02T23:59:59Z
    Fetched from
    http://yr1.i.lencr.org/
    SHA-256
    072639D0B140D5BFFAE16AD9C3F6CC6086040621F51EE61A6D46A8915C07CF76

Path a browser builds: *.badssl.com › YR1 (Let's Encrypt) › Root YR (ISRG) › ISRG Root X1 (Internet Security Research Group)

The fix serve this instead

This is the leaf followed by every intermediate it needs, in order, with no root. Replace the certificate file your server points at with this, keep the private key as it is, reload, and re-run the check.

fullchain.pem
-----BEGIN CERTIFICATE-----
MIIE/zCCA+egAwIBAgISBlxiwdU2G9fmlRJjpnBkBjk0MA0GCSqGSIb3DQEBCwUA
MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD
EwNZUjEwHhcNMjYwOTI5MjAwMjU2WhcNMjYxMjI4MjAwMjU1WjAXMRUwEwYDVQQD
DAwqLmJhZHNzbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCu
GPM37Z6OgQLd6QRN4TUVvc8NxZ1+EtR8M+EWbVWb+2v4LERvxRvN3ByvCAAZTR2O
yMBRb1xbiqmfQt1xzZgFVygC2d8MHiKSfOTXec47ligR62A2fPmeUcMopFNemJzd
SJ2q4RB1sdCutv2eXLEJGtbLsKFCoTBjYNOfg8O4PHI5AL/YATgxLMSieY2mTv6/
fftVZ8lVNa65SwGR9ICtdTGhBy7iifyvcLaDUwvXucpKH6pepYjEUTFLtjws+Q51
5i4RHh0xrqY97P9WG/Vj+Gsm+qYV0FHs/sE+qGJhyEt6teKUIXp8AmRjuJLSylxr
68N2qJiQaGbsBYbXBk2PAgMBAAGjggInMIICIzAOBgNVHQ8BAf8EBAMCBaAwEwYD
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUS+OeWa3e
iWsz0wtEFKK0ww+P/pcwHwYDVR0jBBgwFoAUHy81vkYUgs1Asa55LFV4+vfUaPsw
MwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzAChhdodHRwOi8veXIxLmkubGVuY3Iu
b3JnLzAjBgNVHREEHDAaggwqLmJhZHNzbC5jb22CCmJhZHNzbC5jb20wEwYDVR0g
BAwwCjAIBgZngQwBAgEwLgYDVR0fBCcwJTAjoCGgH4YdaHR0cDovL3lyMS5jLmxl
bmNyLm9yZy81My5jcmwwggENBgorBgEEAdZ5AgQCBIH+BIH7APkAdwDCMX5XRRmj
Re5/ON6ykEHrx8IhWiK/f9W1rXaa2Q5SzQAAAaDu+O+2AAAEAwBIMEYCIQCltbzV
Pre77XfQ7IQXoMW7gL5Klry1UzpTIQxeuLEAxgIhAMSFGiTYkIrojZDTl980aZzL
VO05nug+qoq9H2XtUv4yAH4AJuNkblhpISO8ND9HJDWbN5LNJFqI2BXTkzP9mRir
RyMAAAGg7vjllwAIAAAFAEfwr6MEAwBHMEUCIQDT0xNru0iThHMzOb35RezPBcXW
1xYQIQf67RbwBQ+8VQIgIW/gtjqJZDzMdjSI7iMfy8OGRlDR+5/ennx2I5xCXVQw
DQYJKoZIhvcNAQELBQADggEBAE8xDKe9IJf2o4dDS5yG3I8jYxY9EDp583WtCObO
YvpeLM9lRTunOxD4vGra+5pax687K3NXCIQZS8MjQcNq6NuEqZybtvH06iuRh8s5
AS5qaZYp1QkSQ1SyOzZp93yz7QGG8DMajV3HKkhXQt/Ft0KggqikeaetlHbYcP6b
nT/glbm0cYFNLBesXWSB13/TRyidU6SdWS9vLgFp16e7Fl+QlqnfpsyPwz+dILNz
zg40tolJzZ7yMMIOa0oP3SZUYBNmYtzlVfPHf4f6z5pdydVBqiKQRzxgAX4huw5r
fm/lmKJw34kjJqiPFoi9A07j47PJZ6QptTcVNiP29cdsIAc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE2zCCAsOgAwIBAgIRAKICU/FfJpHAXcHOE7m8yk4wDQYJKoZIhvcNAQELBQAw
LjELMAkGA1UEBhMCVVMxDTALBgNVBAoTBElTUkcxEDAOBgNVBAMTB1Jvb3QgWVIw
HhcNMjUwOTAzMDAwMDAwWhcNMjgwOTAyMjM1OTU5WjAzMQswCQYDVQQGEwJVUzEW
MBQGA1UEChMNTGV0J3MgRW5jcnlwdDEMMAoGA1UEAxMDWVIxMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoVi8X2xCYgMXvJxNPKp/oF13UMgmPABB07VC
LNDtoXmt9luEZNJSBV10VyT1Pz6LD8Zq1d2gc43WNl1AdRrj4sEnazbOiz0nPpmG
Bp2hui49oZtDIY6wdKeZAi5BbNU20CH6RSBBMLSQ9cXrH8dxdv4PAJ45ssGML68U
SE3BsjC2a6cAN9L5CgXVIQi5tfNiTPoFZZ3S0OlXqLmmtdV95udWAb5b6e/F49Di
CsH0Y00Ag72BVIb1hzynmKe+X0mERBTtsb3BwmpV9ipeBjMLoR/D9cHxHQCWoi5l
TmXwY015J5rGelz1nZjJuxc2kioaX29XJBnhMkP531rSdG5uMwIDAQABo4HuMIHr
MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDATASBgNVHRMBAf8E
CDAGAQH/AgEAMB0GA1UdDgQWBBQfLzW+RhSCzUCxrnksVXj699Ro+zAfBgNVHSME
GDAWgBTe51tg0CJtQCh9Pw0B/qS1UrRRlDAyBggrBgEFBQcBAQQmMCQwIgYIKwYB
BQUHMAKGFmh0dHA6Ly95ci5pLmxlbmNyLm9yZy8wEwYDVR0gBAwwCjAIBgZngQwB
AgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3lyLmMubGVuY3Iub3JnLzANBgkq
hkiG9w0BAQsFAAOCAgEA0+zvMq3kHig1ddTmmm+RibTr9/RpX7k4buanMMRqbV/y
IvP82zAHN3mvaw+cASuVsdpd0ikjhr4hnhJQLQOzOp2ccKrsdGOAgo0vddeISFAq
EWEV4lmUM3vFF796up+bSgmJ1u6RupDCMxDgF8M3eLvGuj6L0lu3zkQ0KuQLnKxL
tB0oQqn1Idg5CuuGpMvQzk29Pa3D/qHurc0EIM9SxukQuJqq63lxsYyRQFU8yMBO
hq1w5LbfaWNRrz1uklOfI/pYkAb2E2MTZrAMQkBIE2S8Jt1F8gRc96o/xOsrgvSk
a84AisX6xq1lz1Z7jGvrnXc4TMcjxZTjiTaihcYI1JIXZiLtEMSCa5l3cu8YWd6z
dLRQlqRdclVjuQfNHawRJ6GWlkK0QJosivTKwdBw3KxEtzGo8yMHERbsy57gP1UX
HOMcmZYQC0gtyR3SxfenIM/MxC3Ia2Ypab/kQ/CTnlIn2KQ5JUC6NYrGCbhFN9bp
5lKJStEwCUnLpntcrXk5XVDCNv/5RyWpRThkGOV7GetKkQ0qAY8hCzWK6oqnAhDZ
cjlYVdWfqOw3DIOX6EDNBgAqHarRVxyF9QZdOaXSyPJ0ueD2BYJEBgaCGQ8rAaU/
Qc123V5LTXDZW4CcsPBDyhy4v+c8hClAyw/IkJlfBqxB9D+/wvIMHgECZ4ptP6o=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----

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

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

curl -vI https://incomplete-chain.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.

Every parameter, the JSON field names, rate limits and ready-made CI checks are in the API and curl reference.