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.
| Client | Self-signed certificate | Private CA, root not installed | Intermediate missing |
|---|---|---|---|
| Chrome, Edge | NET::ERR_CERT_AUTHORITY_INVALID | NET::ERR_CERT_AUTHORITY_INVALID | Often loads anyway: Chrome fetches the missing certificate itself |
| Firefox | MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT | SEC_ERROR_UNKNOWN_ISSUER | Often loads anyway: Firefox preloads the intermediates of public CAs |
| openssl s_client | verify error:num=18:self-signed certificate | verify error:num=20:unable to get local issuer certificate | Verify return code: 21 (unable to verify the first certificate) |
| curl | curl: (60) SSL certificate problem: self-signed certificate | unable to get local issuer certificate | unable to get local issuer certificate |
| Python | certificate verify failed: self-signed certificate | certificate verify failed: unable to get local issuer certificate | The same as the private CA case |
| Node.js | DEPTH_ZERO_SELF_SIGNED_CERT | UNABLE_TO_GET_ISSUER_CERT_LOCALLY | UNABLE_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 notice | Where the problem is | Section |
|---|---|---|
| Every HTTPS site fails on this device | Software on the device that intercepts HTTPS, or a badly wrong clock | Interception |
| One site fails, on every device and network | The site's certificate or chain | Site owner |
| Sites fail only at work, school or on one Wi-Fi | A filtering proxy or a Wi-Fi login page on that network | Interception |
| Browsers work, but curl, an app or a webhook fails | The intermediate certificate is missing on the server | Cause 2 |
| Only an old phone, TV or computer fails | An outdated list of trusted roots on that device | Old 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:
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:
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:
* 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 prints | What it means | Cause |
|---|---|---|
verify error:num=18:self-signed certificate | The certificate at depth 0 signed itself | Cause 1 |
num=20 and num=21 at depth=0, return code 21 | Nothing links your certificate to a root: the intermediate is missing | Cause 2 |
num=20 at depth=1 or higher | The chain is complete but ends at a root this machine does not trust | Cause 3 |
verify error:num=19:self-signed certificate in certificate chain | The server also sends a root that is not in the trust store | Cause 3 |
Verify return code: 0 (ok) | Trusted from here. If browsers still fail, they reach another certificate | Cause 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:
sudo apt update && sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.comThe 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:
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):
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.pemSSLCertificateFile /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 SSLCertificateFileTest 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:
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt -untrusted chain.pem cert.pem
# cert.pem: OKWithout the intermediate the same check fails. With our test files it printed:
CN = example.test
error 20 at 0 depth lookup: unable to get local issuer certificate
error leaf.pem: verification failedCause 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:
# 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.pyFirefox 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
--stagingor--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:
# 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 -issuersudo 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,wwwincluded.curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/prints a status code without-kand without--cacert.- Nothing in your code papers over the error.
curl -k, Python’sverify=FalseandNODE_TLS_REJECT_UNAUTHORIZED=0switch 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.