Skip to main content

    Certificate Trust Stores: Why One Certificate Works in Chrome and Fails in Java

    Chrome, Firefox, Java, Node and curl each trust a different list of root CAs. Why one certificate passes in one and fails in another, and how to check.

    MS
    My-SSL Team
    ·
    15 min read
    ·
    Published August 12, 2026
    ·
    Last updated August 12, 2026

    The short answer

    A certificate trust store is the list of root CA certificates a program is willing to treat as a starting point for trust. The catch is that there is no single list per machine. As of August 2026, Chrome carries its own Chrome Root Store on every platform except iOS, Firefox uses Mozilla's bundled list, Windows and macOS each maintain their own, and Java, Node.js, Python and curl all consult separate bundles again. A certificate that validates in your browser can still be rejected by an application on the same computer, because the two never consulted the same list.

    One server certificate checked by six different validators, each consulting its own separate list of trusted root certificatesA single server certificate at the top is offered unchanged to six validators arranged in a grid below it. Chrome, on every platform except iOS, consults the Chrome Root Store that ships with the browser. Firefox consults Mozilla's own bundled list. Safari and everything on iOS consult the Apple root store held in the keychain. Windows applications and .NET consult the Microsoft Trusted Root Program store, which can also fetch roots on demand. Java, highlighted in gold, consults a cacerts keystore that ships inside each installed JDK and is refreshed only when the JDK itself is updated. Node.js, Python and curl each consult a bundle of their own. A closing bar states the consequence: the certificate never changes, so a disagreement between two of these verdicts is a disagreement between two lists, not a fault in the certificate.There is no "the" trust store — there is one per validatorONE server certificateleaf + intermediate, sent identically to everyone…and then six separate lists decide what it is worthChromeChrome Root Storeships with the browserevery platform except iOSupdates on Chrome's scheduleFirefoxMozilla's own listbundled, all platformsignores the OS store bydefaultSafari · everything on iOSApple root storein the keychainupdates with the OSWindows apps · .NETMicrosoft Trusted Rootcertlm.msccan fetch roots on demand,so the list looks shortJavacacertsinside every installed JDKrefreshed only when youupdate the JDKNode · Python · curla bundle eachNode compiles one inPython reads certificurl reads the OS bundleTHE CERTIFICATE IS THE SAME IN ALL SIX CASESSo when two of these disagree, the disagreement is between two lists — not evidence that anything is wrong with the certificate.
    The lists overlap heavily, which is why this almost never comes up. It surfaces at the edges: a newly added root, an old JDK, a stripped-down container image.

    Most of the time none of this matters, because the major lists overlap almost completely. It starts to matter at the edges — a root added to the public programs recently, a build server running a JDK nobody has touched in three years, a container image trimmed down until the CA bundle went with it. What follows is how the lists differ, how to read the contents of the ones on your own machine, and how to tell this failure apart from the far more common one it gets mistaken for.

    What is a certificate trust store?

    A certificate trust store is a collection of root CA certificates that a program treats as trust anchors. When a TLS client validates a server certificate, it builds a path from that certificate up through any intermediates to a root, and the check only succeeds if that root is already in the store. Nothing about the path is trusted on its own merits; the store is what makes the end of it authoritative.

    Path validation itself is specified in RFC 5280, and the specification is careful to treat the set of trust anchors as an input to the algorithm rather than part of it. That distinction is the whole subject of this article. Two clients can implement identical, correct path validation and still disagree, because they were handed different inputs.

    A root gets into a store by being accepted into a root program — the vetting process run by Microsoft, Apple, Mozilla, Google and the Android project. Each program keeps its own criteria and its own inclusion timeline, and each ships its results on its own release schedule. Six programs, six schedules, and a certificate that has to satisfy all of them.

    Which programs keep their own trust store?

    More than people expect. Both major browsers ship their own list rather than deferring to the operating system, both mobile platforms maintain their own, and the common server-side runtimes each carry a bundle of their own as well. A typical developer laptop is running at least six independent lists, and a build server usually adds a few more inside container images.

    ValidatorStore it usesWhen it changes
    ChromeChrome Root Store, on Windows, macOS, Linux, ChromeOS and Android. On iOS it uses the system store instead, because Apple's platform rules require it.With Chrome
    FirefoxMozilla's own list, bundled with the browser on every platformWith Firefox
    Safari, and all of iOSApple root store, in the keychainWith the OS
    Windows apps, .NET, PowerShellMicrosoft Trusted Root Program store, viewable in certlm.msc. Windows can also retrieve a root on demand, which is why the visible list looks far shorter than the program itself.With Windows Update
    JavaA cacerts keystore inside each installed JDK, under $JAVA_HOME/lib/securityOnly when you update the JDK
    Node.jsA Mozilla-derived bundle compiled into the binary. Node v22.19 and v24.6 added --use-system-ca (and NODE_USE_SYSTEM_CA=1) to consult the OS store as well.With the Node release
    Python (requests, urllib3)The certifi bundle, installed per virtual environmentWhen you upgrade certifi
    curl and OpenSSL on LinuxThe distribution's CA bundle, usually from the ca-certificates packageWith the package manager
    Android appsThe system store. Apps targeting API level 24 and above do not trust user-installed CAs unless the developer opts in through a network security configuration.With the OS

    Two rows in that table cause most of the surprises. Java's is the one people forget exists at all, because it updates on a schedule that has nothing to do with the operating system underneath it. Windows' is the one people misread, because the on-demand retrieval means an empty-looking list is not evidence that a root is missing.

    Why does the same certificate pass in Chrome and fail in Java?

    Because the two consulted different lists, and one of them did not contain the root. There are only three ways that happens: the root was never in that list, the list is an old copy that predates the root, or the program was pointed at a different file entirely. All three produce a near-identical error message, and they have completely different fixes.

    The three reasons two validators on one machine reach different verdicts about the same certificateThree panels side by side. The first: the root is simply absent from that particular list, which is what happens with a private or internal certificate authority, and the fix is to add the root deliberately. The second, highlighted in gold, is a stale list — the root exists but the validator's copy predates it, which is common with a pinned JDK or an old container base image, and the fix is to update the runtime rather than the operating system. The third: the program was pointed somewhere else entirely by an environment variable such as SSL_CERT_FILE, REQUESTS_CA_BUNDLE or NODE_EXTRA_CA_CERTS, or the image never installed a CA bundle at all, so there are no trust anchors to build a path to. A closing bar gives the way to tell them apart: absent and stale fail for one certificate, while a missing bundle fails for every TLS connection the program makes.Three reasons two lists disagree1 — NEVER THEREThe root is absentPrivate or internal CA,or a self-signed certificate.No public program evercarried it, so no list has it.Fix: add the root, onceper store that needs it.2 — THERE, BUT OLDThe list is staleThe root is public andwidely trusted — this copyof the list just predates it.Pinned JDK · old base imageFix: update the runtime,not the operating system.3 — WRONG FILEPointed elsewhereSSL_CERT_FILEREQUESTS_CA_BUNDLENODE_EXTRA_CA_CERTS…or the image never hadca-certificates installed.Fix: install the bundleor unset the variable.HOW TO TELL 3 FROM 1 AND 2 IN ONE STEPReach a large, obviously trusted public site from the same program. If that fails too, the bundle is missing or misdirected.
    Cause two is the expensive one. Nothing on the machine looks broken, the server tests clean from a browser, and the only thing at fault is the age of a file inside a runtime nobody thought to check.

    The stale case is worth dwelling on, because it is the one that survives a whole afternoon of debugging. A build agent pinned to an old JDK is validating against a cacerts file that was assembled when that JDK was built. The operating system beneath it has been patched continuously and knows about the root perfectly well. Nothing on the machine is misconfigured in any way an audit would flag, and the certificate is entirely valid. The file is just old.

    The third cause shows up most often in containers. A minimal base image frequently ships without the ca-certificates package, which leaves OpenSSL with no trust anchors at all — so every TLS connection from that image fails, not just yours. If a program cannot reach any well-known public site either, stop looking at your certificate and go and look at the image.

    How to check what your system actually trusts

    Ask each runtime directly rather than reasoning about what it should be using. Every store on a machine can be enumerated from the command line in one line, and the counts they report will not match each other. Seeing three different numbers on one machine is usually the moment the problem stops being mysterious.

    One line per store

    # OpenSSL / curl — where the system bundle lives, and how many roots it holds
    openssl version -d
    grep -c 'BEGIN CERTIFICATE' /etc/ssl/certs/ca-certificates.crt
    
    # Node.js (v22.19+ / v24.6+) — compare the compiled-in list with the OS list
    node -e "const t=require('tls');console.log('bundled', t.getCACertificates('bundled').length)"
    node -e "const t=require('tls');console.log('system ', t.getCACertificates('system').length)"
    
    # Python — which bundle requests will actually read
    python3 -c "import certifi; print(certifi.where())"
    
    # Java 9 and later — the JDK's own keystore
    keytool -list -cacerts | head
    
    # What the server is really sending, with no client-side help
    openssl s_client -connect example.com:443 -showcerts

    Running these together on one Linux machine while preparing this article, Node's compiled-in list, the distribution's ca-certificates bundle and the certifi bundle in a Python environment each reported a different number of root certificates — three files, three counts, one computer. The absolute numbers are not worth quoting, because they shift with every distribution, runtime version and corporate image. The disagreement is the point, and it reproduces anywhere you care to try it.

    The last command is the one to reach for first when a server is involved. openssl s_client does not go and fetch anything the server failed to send, so what it prints is exactly what the server offered — which is what separates the next question.

    Trust store problem, or a missing intermediate?

    Look at which clients fail. A missing intermediate certificate fails in curl, mobile clients and Java while desktop browsers stay green, because browsers can fetch the absent intermediate themselves and paper over the gap. A trust store problem does the opposite: one runtime fails while everything else on the same machine is perfectly happy.

    A decision tree separating a missing intermediate certificate from a trust store problem from a fault in the certificate itselfStarting from a client rejecting your certificate, the tree asks which clients fail. If browsers succeed while curl, mobile clients and Java fail, the cause is a missing intermediate certificate on the server, because desktop browsers can fetch the missing intermediate themselves and quietly hide the gap; the fix is to serve the full chain. If exactly one runtime fails while everything else on the same machine succeeds, the cause is that runtime's trust store, highlighted in gold, and the fix is to update or extend that specific list. If every client fails, the certificate itself is the problem — expired, issued for a different hostname, or self-signed — and no trust store change is appropriate.Same complaint, three different faults — ask who fails"It says the certificate isn't trusted"Now find out which clients actually fail.BROWSERS FINE, OTHERS FAILMissing intermediatecurl, mobile and Java fail;desktop Chrome looks fine.Browsers fetch the gapthemselves and hide it.ONE RUNTIME FAILSTrust storeEverything else on the samemachine is happy.Fix that one list —the server is not at fault.EVERYTHING FAILSThe certificateExpired, wrong hostname,or self-signed.Do not touch anytrust store for this.THE ONE COMMAND THAT SPLITS THESE APARTopenssl s_client -connect example.com:443 -showcertsIt does no AIA fetching, so it shows exactly what the server sent — and nothing the client filled in on the server's behalf.
    Worth doing in this order. Changing a trust store to work around what is really a missing intermediate fixes one machine and leaves every other client still failing.

    Getting this the wrong way round is expensive. Adding a root to one machine's trust store to work around what is really a missing intermediate does make that machine work, which is exactly why the mistake sticks — the ticket closes, and every other client in the world carries on failing. The server-side fix is covered in detail in our guide to the SSL certificate chain and how to repair a broken one.

    There is a third outcome worth naming, because it gets treated as a trust store issue more often than it should. If every client fails, the certificate itself is the problem — expired, issued for a different hostname, or self-signed. No amount of editing trust stores is the right response to that, and doing it anyway buries a real defect under a local workaround.

    How to add a root certificate to a trust store

    Once per store that needs it, using that store's own tooling. There is no command that updates all of them together, which is the practical cost of running a private CA: every list on every machine is a separate deployment target, and one missed runtime produces a failure that looks like a certificate problem.

    StoreHow to add a root
    Debian / UbuntuCopy the PEM into /usr/local/share/ca-certificates/ with a .crt extension, then run update-ca-certificates
    RHEL / FedoraCopy into /etc/pki/ca-trust/source/anchors/, then run update-ca-trust
    Windowscertlm.msc → Trusted Root Certification Authorities, or certutil -addstore -f Root ca.crt. Group Policy for fleets.
    macOSKeychain Access → System, then set the certificate to Always Trust
    Javakeytool -importcert -cacerts -alias internal-root -file ca.crt — repeat for every JDK on the machine
    Node.jsNODE_EXTRA_CA_CERTS=/path/ca.pem, or --use-system-ca on v22.19+ / v24.6+ to pick up the OS store
    PythonREQUESTS_CA_BUNDLE or SSL_CERT_FILE pointing at a bundle that includes your root
    AndroidShip a network security configuration that names the root as a trust anchor — installing it in Settings will not reach apps

    Before you add one

    A root in a trust store is trusted for every certificate it ever signs, for any hostname, until someone removes it. Adding your own internal CA to machines you administer is a reasonable engineering decision. Accepting a root that arrived with a piece of software, or that someone sent you to make an error go away, hands whoever holds that key the ability to present a valid-looking certificate for any site at all. Keep a record of which roots you added and where, and take them out when the reason for them ends.

    What this means when you buy a certificate

    The distribution problem is the part you are handing over. A root belonging to an established public CA is already present in the Microsoft, Apple, Mozilla, Chrome and Android programs, and it reaches devices through ordinary updates that nobody at your end has to arrange. Your server sends the leaf and the intermediates, and the client already holds the anchor.

    Issue from your own CA instead and every list in the table above becomes yours to maintain, on every machine that will ever connect, for as long as the CA exists. That is a sensible trade for internal service-to-service traffic where you control both ends. It is a poor one for anything the public reaches, which is what a publicly trusted SSL certificate exists to avoid. The same reasoning, in more detail, is in our write-up on when a self-signed certificate is and isn't appropriate.

    One practical check before committing to any CA: confirm its roots are carried by all the programs your users actually run, not only the desktop browsers. Mobile and Java clients are where a thin root distribution shows up first, and it shows up as your problem rather than the CA's.

    Frequently Asked Questions

    Get instant answers to common questions about SSL certificates and our services.

    Still Have Questions?

    Our SSL experts are available 24/7 to help with any questions about certificates, installation, or technical issues.

    Choosing a certificate for a public service

    If the clients you need to satisfy include phones, Java applications and things you will never see, a certificate from a publicly trusted CA removes the distribution work described on this page. Our SSL certificate range covers the validation levels, and the comparison in which SSL certificate do I need walks through picking between them.

    Related reading