Troubleshooting

NET::ERR_CERT_AUTHORITY_INVALID: what it means and how to fix it

NET::ERR_CERT_AUTHORITY_INVALID means the browser cannot chain the certificate to a trusted root. Fixes for visitors and site owners, with openssl checks.

Published ~11 min read
On this page

Short answer: NET::ERR_CERT_AUTHORITY_INVALID means the browser could not link the site’s certificate to a root it trusts. The certificate is self-signed, comes from a private CA, lacks its intermediate, or was replaced by antivirus or a company proxy. If everyone sees it, the site must serve a publicly trusted certificate with its full chain.

Chrome and Edge show the code under “Your connection is not private”. Under Advanced, Chrome adds a sentence such as “This server could not prove that it is example.com; its security certificate is not trusted by your computer’s operating system”, worded slightly differently per version and platform. Chromium’s own source describes error -202 as a certificate “signed by an authority we don’t trust”. Firefox says SEC_ERROR_UNKNOWN_ISSUER, or MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT when the certificate signed itself. Expired certificates and certificates for another name have codes of their own, listed in our guide to “Your connection is not private”. This page is about trust only.

The same failure outside the browser

Apps, scripts and webhooks fail on the same certificates with their own words. We produced the rows for openssl, curl, Python and Node.js in a scratch directory on Debian 12 (OpenSSL 3.0.22, curl 7.88.1, Python 3.11.2, Node.js 25.7.0), against a test server on 127.0.0.1 with a throwaway root, intermediate and leaf. The browser rows are the codes the browsers use; we did not run a browser here.

ClientSelf-signed certificatePrivate CA, root not installedIntermediate missing
Chrome, EdgeNET::ERR_CERT_AUTHORITY_INVALIDNET::ERR_CERT_AUTHORITY_INVALIDOften loads anyway: Chrome fetches the missing certificate itself
FirefoxMOZILLA_PKIX_ERROR_SELF_SIGNED_CERTSEC_ERROR_UNKNOWN_ISSUEROften loads anyway: Firefox preloads the intermediates of public CAs
openssl s_clientverify error:num=18:self-signed certificateverify error:num=20:unable to get local issuer certificateVerify return code: 21 (unable to verify the first certificate)
curlcurl: (60) SSL certificate problem: self-signed certificateunable to get local issuer certificateunable to get local issuer certificate
Pythoncertificate verify failed: self-signed certificatecertificate verify failed: unable to get local issuer certificateThe same as the private CA case
Node.jsDEPTH_ZERO_SELF_SIGNED_CERTUNABLE_TO_GET_ISSUER_CERT_LOCALLYUNABLE_TO_VERIFY_LEAF_SIGNATURE

Java reports the same family as PKIX path building failed with unable to find valid certification path to requested target. The last column explains a classic support ticket: the site works in every browser, yet curl, a payment provider’s webhook or an Android app refuses it.

What does ERR_CERT_AUTHORITY_INVALID mean?

A site sends its own certificate (the leaf) and one or more intermediate certificates. Each one is signed by the next one up. The browser follows those signatures until it reaches a root certificate in its trust store, a list of public certificate authorities that ships with the browser or the operating system. If the path ends anywhere else, at a certificate that signed itself or at an issuer the browser cannot find, the certificate is untrusted, however correct its dates and names are.

Which list counts depends on the client. Per Chromium’s Chrome Root Store FAQ, Chrome checks against its own list on Windows and macOS (since Chrome 108), Linux and ChromeOS (114) and Android (115), not on iOS. It still accepts roots that an administrator or a program added to the system’s store, which is how company proxies and antivirus products get their certificates accepted. Firefox keeps a list of its own. Command-line tools use the operating system’s list, unless the language brings one: Node.js and Python’s requests library ship their own bundles. So the same certificate can pass in one program and fail in the next.

Is it your device or the site?

What you noticeWhere the problem isSection
Every HTTPS site fails on this deviceSoftware on the device that intercepts HTTPS, or a badly wrong clockInterception
One site fails, on every device and networkThe site's certificate or chainSite owner
Sites fail only at work, school or on one Wi-FiA filtering proxy or a Wi-Fi login page on that networkInterception
Browsers work, but curl, an app or a webhook failsThe intermediate certificate is missing on the serverCause 2
Only an old phone, TV or computer failsAn outdated list of trusted roots on that deviceOld devices

Visitor fixes: when only you see it

Rule out the clock

A clock that is days or years off breaks certificate checks on every HTTPS site. It usually shows up as its own code, NET::ERR_CERT_DATE_INVALID, not as this one. It takes ten seconds to check: turn on automatic date, time and time zone in your system settings and reload. If other sites show the date error, fix that first and this one may go with it.

Look at who issued the certificate

Click Not secure in the address bar, then Certificate is not valid, and read the Issued by field. A public site’s certificate names a public CA. If the issuer names your antivirus, a firewall, your employer or your school, something on your device or network opens every HTTPS connection, inspects it and re-signs it with its own root. When that root is missing from the browser, for example in Firefox with its separate list or in a fresh browser profile, every site fails with this code.

  • Antivirus or a security suite: update it, or switch off its HTTPS, SSL or web scanning option for a test. Chrome often recognises such products and shows “An application is stopping Chrome from safely connecting to this site” instead; Firefox shows MOZILLA_PKIX_ERROR_MITM_DETECTED.
  • A company or school network: the IT department has to install its inspection root on managed devices. On your own device, do not import it unless you accept that they can read your HTTPS traffic.
  • Hotel, train or airport Wi-Fi: the network intercepts your first request to show its login page. Open a plain-HTTP address such as http://neverssl.com, accept the terms, then retry.

Old devices and outdated root lists

A phone, TV or computer that no longer gets updates keeps the list of roots it shipped with. Sites whose certificates chain to a newer root then fail on that device only. Update the system and the browser. Where the system cannot be updated, a current Firefox or Chrome often helps, because both bring their own list of roots.

Your own router, NAS or server panel

Routers, NAS boxes and admin panels such as Proxmox ship with self-signed certificates, so this code is expected on them. Install that device’s certificate as trusted on your own computer, or put the panel behind a reverse proxy with a Let’s Encrypt certificate, so the warning keeps its meaning everywhere else.

Site owner: read the chain your server sends

Check from outside, from a network without HTTPS inspection, what the server actually sends. The first command lists every certificate with its subject (s:) and issuer (i:) and prints the verification result; the second shows how curl sees it:

any Linux · OpenSSL 1.1.1 or later
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>&1 \
  | grep -E '^ *[0-9] s:|^ +i:|^depth|^verify error|Verify return code'

curl -sv -o /dev/null https://example.com/ 2>&1 | grep -E '^\* (SSL|TLS)|^\*  |^curl'

This is the first command against our test server, which sends a complete chain under a root that is not in Debian’s trust store, the situation of a private CA:

Example output · complete chain, untrusted root
depth=1 CN = Test Intermediate CA
verify error:num=20:unable to get local issuer certificate
depth=0 CN = example.test
 0 s:CN = example.test
   i:CN = Test Intermediate CA
 1 s:CN = Test Intermediate CA
   i:CN = Test Root CA
Verify return code: 20 (unable to get local issuer certificate)

Read it from the bottom of the list: the issuer of the last certificate has to be a root the client trusts. Here it is Test Root CA, which nobody trusts, and the depth=1 line above the error names the certificate whose issuer could not be found. curl stops at the same point:

Example output · curl -v, same server
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: unable to get local issuer certificate
openssl printsWhat it meansCause
verify error:num=18:self-signed certificateThe certificate at depth 0 signed itselfCause 1
num=20 and num=21 at depth=0, return code 21Nothing links your certificate to a root: the intermediate is missingCause 2
num=20 at depth=1 or higherThe chain is complete but ends at a root this machine does not trustCause 3
verify error:num=19:self-signed certificate in certificate chainThe server also sends a root that is not in the trust storeCause 3
Verify return code: 0 (ok)Trusted from here. If browsers still fail, they reach another certificateCause 4

Without a terminal, our SSL checker runs the same handshake from Amsterdam and shows the chain exactly as your server sends it, and whether it ends at a trusted root.

The openssl commands guide explains the other s_client and x509 options used here.

Cause 1: a self-signed or placeholder certificate

The server answers with a certificate it made for itself. Typical sources: the snakeoil certificate from Debian’s ssl-cert package (its issuer is the server’s own hostname), a default certificate generated by a control panel, a hand-made openssl req -x509, and the fallback certificates of reverse proxies, such as Kubernetes Ingress Controller Fake Certificate from ingress-nginx or TRAEFIK DEFAULT CERT. Those last two mean the proxy has no certificate for the name that was requested, so check its configuration for that host before anything else.

For a public site the fix is a free Let’s Encrypt certificate. With nginx and certbot, once the DNS records point at the server and port 80 is open:

Debian · Ubuntu · nginx
sudo apt update && sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

The full setup with renewal is in our nginx reverse proxy guide.

Cause 2: the intermediate certificate is missing

The server sends only its own certificate, so nothing connects it to a root. Browsers often hide this: Chrome downloads the missing intermediate from the address written in the certificate, and Firefox preloads the intermediates of the public CAs. Clients that do neither fail, which is why the site looks fine in your browser while curl, Python, Java, webhooks and many apps reject it. When the download is blocked, browsers fail too.

Count what the server sends. A correct chain from a public CA gives 2 or more:

any Linux
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
  | grep -c 'BEGIN CERTIFICATE'

The fix is to point the server at the file that contains your certificate followed by the intermediates. With certbot that file is fullchain.pem; with a paid certificate, put the CA’s bundle after your certificate, in that order (nginx wants the server certificate first, then the intermediates):

nginx · server block
ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;   # not cert.pem
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# paid certificate: build the file yourself, your certificate first
# cat example_com.crt ca-bundle.crt > /etc/ssl/example.com/fullchain.pem
Apache 2.4.8 or later · VirtualHost *:443
SSLCertificateFile    /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# SSLCertificateChainFile is obsolete since 2.4.8; the chain goes into SSLCertificateFile

Test and reload (sudo nginx -t && sudo systemctl reload nginx, or sudo apachectl configtest && sudo systemctl reload apache2), then run the count again.

Check a chain file before you deploy it

openssl verify checks files on disk against Debian’s or Ubuntu’s list of public roots, with the intermediates passed through -untrusted:

Debian · Ubuntu
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt -untrusted chain.pem cert.pem
# cert.pem: OK

Without the intermediate the same check fails. With our test files it printed:

Example output · leaf without its intermediate
CN = example.test
error 20 at 0 depth lookup: unable to get local issuer certificate
error leaf.pem: verification failed

Cause 3: a private CA, an internal network or a test certificate

Certificates from your own CA (Active Directory Certificate Services, step-ca, mkcert or a hand-built one) are only trusted where you installed that CA’s root. For an internal site that is fine; for anything the public reaches, use a public certificate. Internal names under a domain you own can get Let’s Encrypt certificates through the DNS challenge, without being reachable from the internet.

To trust your own root on the machines that need it:

trust a private root CA · file name internal-ca.crt
# Debian, Ubuntu (the file must be PEM and end in .crt)
sudo cp internal-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

# Windows, Command Prompt as administrator
certutil -addstore -f Root internal-ca.crt

# macOS
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain internal-ca.crt

# Node.js uses its own bundle; add the root for one process
NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/internal-ca.crt node app.js

# Python requests uses its own bundle; point it at the system one
REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt python3 script.py

Firefox needs the root imported in its own settings, under Privacy & Security, Certificates, View Certificates, Authorities. Node.js 25.7.0 suggested --use-system-ca in its own error message in our test; that flag makes it read the system store instead.

  • A Let’s Encrypt staging certificate. Certificates from the staging environment (certbot’s --staging or --test-cert) have an issuer whose name starts with (STAGING) and are trusted by nobody. Request the certificate again without that flag.
  • A Cloudflare Origin CA certificate seen directly. It is trusted by Cloudflare only, so visitors get this error as soon as the record is set to DNS only. Details in the Origin CA section of our certificate errors guide.

Cause 4: the wrong certificate on the virtual host

Your certificate is fine, but some visitors reach a different one. nginx hands names without a matching server_name to its default server, Apache to the first VirtualHost on *:443, and those often carry a placeholder. Two common triggers: a server block with listen 443 ssl; but no listen [::]:443 ssl;, so IPv6 visitors land on the default, and an old A or AAAA record that still points at a previous server. Compare what each path returns:

any Linux · 203.0.113.10 and 2001:db8::10 stand for your addresses
# With the name, as browsers ask
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer

# Without a name: what the default server hands out
openssl s_client -connect example.com:443 -noservername </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer

# Each address on its own
openssl s_client -connect 203.0.113.10:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer
openssl s_client -connect '[2001:db8::10]:443' -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer

sudo nginx -T | grep -nE 'server_name|listen|ssl_certificate' then shows which block holds which certificate. A default server that refuses unknown names, instead of handing out a placeholder, is part of the configuration in our SSL handshake guide.

After the fix

  • Verify return code: 0 (ok) from a machine outside your network, for every name you serve, www included.
  • curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/ prints a status code without -k and without --cacert.
  • Nothing in your code papers over the error. curl -k, Python’s verify=False and NODE_TLS_REJECT_UNAUTHORIZED=0 switch off the check completely, so they also accept an attacker’s certificate. Fix the chain or trust the right root instead.

On a VMHeaven Linux VPS you run certbot and the web server as root, so every fix on this page is yours to make; if a configuration change locks you out, the browser VNC console in the panel still reaches the server. On Plesk hosting the free SSL certificate renews automatically, so the server-side steps here are for servers you run yourself.

Frequently asked

What does NET::ERR_CERT_AUTHORITY_INVALID mean?

The browser could not link the site's certificate to a root certificate it trusts. The certificate is self-signed, issued by a private CA, sent without its intermediate certificate, or replaced by antivirus or a company proxy that inspects HTTPS. Expired certificates and certificates for another name have different error codes.

How do I fix ERR_CERT_AUTHORITY_INVALID as a visitor?

Check whether other HTTPS sites fail too. If they do, look at the certificate's issuer: if it names your antivirus, employer or school, HTTPS inspection is the cause, so update or pause that software or ask your IT department. Also set the clock to automatic and update the browser and the system. Do not click Proceed on a site where you log in or pay.

Why does my site work in Chrome while curl says unable to get local issuer certificate?

Your server sends its certificate without the intermediate. Chrome and Firefox often fill the gap themselves, but curl, Python, Java and many apps do not. Point the web server at the full chain, fullchain.pem with certbot, instead of cert.pem, reload it, and check that openssl s_client -showcerts lists at least two certificates.

How do I make a certificate from my own CA trusted?

Install the root certificate on every machine that needs it: on Debian and Ubuntu copy it as a .crt file to /usr/local/share/ca-certificates and run update-ca-certificates, on Windows run certutil -addstore -f Root, on macOS security add-trusted-cert. Firefox, Node.js and Python requests keep their own lists and need it separately. For anything the public reaches, use a publicly trusted certificate instead.

Related articles

All troubleshooting