DEV Community

Merlonix
Merlonix

Posted on Originally published at merlonix.com

NET::ERR_CERT_AUTHORITY_INVALID: When the Root Itself Is Not Trusted

The hostname matches. The chain is complete — every intermediate is present, each certificate signs the next, and it all leads up to a single certificate at the top. The dates are fine. And the browser still slams the door:

Your connection is not private
NET::ERR_CERT_AUTHORITY_INVALID
Enter fullscreen mode Exit fullscreen mode

Every stack says it in its own accent. Chrome and Edge say NET::ERR_CERT_AUTHORITY_INVALID. Firefox says SEC_ERROR_UNKNOWN_ISSUER — or MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT when the leaf signed itself. curl says curl: (60) SSL certificate problem: self-signed certificate in certificate chain. Node says SELF_SIGNED_CERT_IN_CHAIN or DEPTH_ZERO_SELF_SIGNED_CERT. Python says ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain. OpenSSL prints verify error:num=19:self-signed certificate in certificate chain (or num=18 for a self-signed leaf). One root cause, many dialects: the client followed the chain all the way to the top and did not recognize the authority sitting there.

This is a different failure from the two it's most often confused with. It is not a name mismatch (the hostname is fine) and — the subtle one — it is not simply an incomplete chain. A missing intermediate breaks a path to a root the client would have trusted; this is the client reaching the root and refusing it. The chain can be perfect and still end at an anchor the client will never accept.


Trust Is About the Anchor, Not the Path

Certificate validation does two separate things, and it's easy to conflate them. First it builds a path: leaf → intermediate(s) → root, each link signed by the next one up. Then it checks whether the anchor at the top of that path — the root — is one it already trusts, because it ships in the client's trust store (the OS or browser root program) and its public key is baked in.

An incomplete-chain error is a path failure: the client couldn't assemble the links, usually because the server didn't send an intermediate. NET::ERR_CERT_AUTHORITY_INVALID is an anchor failure: the path is fine, the client reached the top, and the certificate up there is not in any trust store it consults. No amount of adding intermediates fixes that — you can hand the client a beautifully complete chain and it will still reject it, because completeness is not the same as being anchored in a trusted root.

That's the whole error in one sentence: the client built a chain to a root it does not trust. The question is never "is this chain complete?" It can be. The question is "does this chain terminate in a root the client already trusts?" — and when the answer is no, you get AUTHORITY_INVALID, no matter how tidy everything below the root looks.

What Actually Causes It

In rough order of how often they bite:

  • A self-signed certificate. The leaf signed itself; there is no separate CA at all. Ubiquitous on localhost, internal dashboards, appliances, IoT devices, staging boxes, and anything stood up with openssl req -x509 in thirty seconds. It's a valid certificate in the cryptographic sense — it just vouches for itself, and self-vouching is exactly what the trust store exists to reject. Firefox names this one specifically: MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT.

  • A private or internal CA the public doesn't ship. A company runs its own certificate authority (Active Directory Certificate Services, a HashiCorp Vault PKI, an internal step-ca) and issues certificates from it. On a managed corporate laptop, that CA's root is pushed into the trust store, so everything works — and it looks like a solved problem. Then someone hits the same host from an unmanaged device, a personal phone, or a CI runner that doesn't have the root, and the certificate is AUTHORITY_INVALID because that client never received the internal root. The certificate didn't change; the audience did.

  • A TLS-inspecting proxy or antivirus re-signing traffic. Corporate middleboxes (Zscaler, a Palo Alto firewall) and some antivirus products terminate TLS, inspect it, and re-sign it with their own CA on the way to you. If that CA's root is installed on the machine — normal for a managed device — it's invisible. If it isn't (a container, a VM, a colleague's BYOD laptop), every HTTPS site suddenly shows AUTHORITY_INVALID, which is the tell: it's not one site's certificate that's broken, it's all of them, because something in the path is re-issuing everything from an untrusted root.

  • A chain that leads to the wrong root, or a genuinely untrusted one. The chain is structurally perfect — intermediates present, hostname covered, nothing self-signed at the leaf — and it still terminates at a root the client's program doesn't carry: a CA that was distrusted and removed, a cross-sign that resolved to a retired root, or a root that some platforms ship and others don't. This is the hardest case to see, because everything below the anchor is flawless and the fixture looks identical to a working certificate right up until the trust check at the very top.

The first three are the common ones, and all three share a shape: the certificate is fine for a client that has the right root, and broken for one that doesn't. Which is why "it works on my machine" is the single most misleading sentence in this whole category.

How to See the Trust Anchor Your Server Presents

Stop asking "is the chain complete?" and start asking "what's at the top of it, and is it public?" One command pulls the full chain the server serves and shows you the verification result and the anchor:

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
Enter fullscreen mode Exit fullscreen mode

Three things to read:

  • The Verify return code line. 0 (ok) means the client (using openssl's default trust store) reached a trusted root. 19 (self-signed certificate in certificate chain), 18 (self-signed certificate), or 20 (unable to get local issuer certificate) each tell you where it stopped. Code 19 in particular is the classic AUTHORITY_INVALID shape: a full chain that ends in a self-signed root openssl doesn't trust.

  • The topmost certificate in -showcerts. Read the s: (subject) and i: (issuer) of the last certificate in the printed chain. If its subject equals its issuer, that's a root — and now the only question is whether that root is one the public trust stores carry. A well-known public CA name (ISRG Root X1, DigiCert, Google Trust Services) means the problem is elsewhere; an unfamiliar name, a company name, or a proxy vendor's name means you've found your untrusted anchor directly.

  • Whether it's this site or every site. If AUTHORITY_INVALID appears on every HTTPS site from one machine, you're not looking at a server problem — you're behind a TLS-inspecting proxy or antivirus re-signing traffic with a root that machine doesn't trust. If it's one host, it's that host's chain or a private CA. That single distinction saves hours.

The tell that separates this from a missing-intermediate error: with an incomplete chain, adding the intermediate and re-running gets you to Verify return code: 0, because the root was trusted all along. With AUTHORITY_INVALID, you can hand the client a complete chain and the return code stays non-zero — because the thing it doesn't trust is the root, and you can't add your way to trusting a root the program has chosen not to carry.

What This Means If You Operate the Server

  • Decide whether this endpoint is public or private, and issue accordingly. If real users on devices you don't control will reach it, it needs a certificate from a publicly trusted CA (Let's Encrypt, ZeroSSL, or a commercial CA) — a self-signed or private-CA certificate will fail for exactly the audience you can't reconfigure. Reserve self-signed and internal-CA certificates for endpoints where you control every client's trust store.

  • If you run a private CA, ship the root to every client that must trust it — and count the clients you don't manage. The certificate works on the golden laptop image and fails everywhere else because the root only reached the managed fleet. CI runners, containers, contractors' machines, and mobile devices each need the root installed, or they each need a publicly trusted certificate instead.

  • Don't confuse "chain complete" with "chain trusted." Serving the full intermediate chain is necessary and it fixes the incomplete-chain error — but if the root at the top isn't public, the certificate is still AUTHORITY_INVALID. The two problems look adjacent and have different fixes.

  • Watch the anchor, not just the expiry date. A certificate can renew on schedule, keep a matching hostname, and still start failing the day it's re-issued from an internal CA by mistake, or the day a public root it depended on is distrusted and removed from the trust programs. Monitoring that only reads notAfter will call that certificate healthy while real clients get AUTHORITY_INVALID.

What This Means If You Consume One

  • Don't reach for -k or rejectUnauthorized: false as the fix. curl -k, NODE_TLS_REJECT_UNAUTHORIZED=0, verify=False — each one turns off the exact check that just told you the endpoint's certificate doesn't chain to a root you trust, which is precisely the signal you want on when a middlebox could be re-signing your traffic or you could be talking to the wrong host.

  • If it's an internal service, install the root the right way. When you legitimately need to trust a private CA, add its root to your trust store (the OS store, or NODE_EXTRA_CA_CERTS / REQUESTS_CA_BUNDLE / SSL_CERT_FILE for a single tool) rather than disabling verification globally. That trusts one authority you've vetted instead of trusting everything.

  • If every site is failing, look at your own machine first. AUTHORITY_INVALID on all HTTPS traffic means a proxy or antivirus is re-signing with a root your environment lacks — the fix is on your side (install the proxy root, or run outside the inspecting network), not the servers'.

  • If it's a third party's public endpoint, the report writes itself. "Your endpoint's chain terminates in CN=<some private CA>, which isn't in the public trust stores — openssl s_client returns Verify return code: 19 and the topmost cert is self-issued" is a precise, actionable bug, and far better than routing around it with verification off.


NET::ERR_CERT_AUTHORITY_INVALID is the browser refusing a certificate whose chain it followed all the way to a root it doesn't trust — and it is right to. The certificate may be complete, name-matching, and unexpired; it just asks you to trust an authority the client has no reason to. The trap is that it looks like a chain problem you can fix by adding an intermediate, when the actual repair is either getting a certificate from a root the audience already trusts, or getting the private root you're using into the trust stores of every client that has to reach it.

Merlonix reads the certificate chain your server actually presents — from outside your stack, from a client with a standard public trust store — and tells you in plain language when the chain terminates in a root that public clients won't trust, distinguishing an untrusted anchor from a merely missing intermediate so you know which fix you need. You can check a domain's live certificate and DNS right now without signing up, watch a certificate's expiry with the free cert watcher, and the free tools hub has the rest. For the two errors this one gets confused with, why a certificate that works in your browser fails in curl covers the missing-intermediate case and SSL certificate name mismatch covers the wrong-hostname case; what happens when an SSL certificate expires covers the date case.

A trusted certificate is one whose chain ends where the client already keeps its trust. When the browser says the authority is invalid, it isn't being difficult — it's telling you the top of your chain and the client's trust store have never been introduced, and the only place to fix that is where trust actually lives: the root, and who ships it.

Top comments (0)