ToolSura
    HomeTools
    Blog
    ToolSuraPrivacy-First Tools

    Free utilities that run in your browser. No trackers, no accounts, no uploads.

    All Systems Operational

    Product

    • Free Online Tools
    • Contact
    • FAQs
    • About

    Legal

    • Privacy Policy
    • Cookie Policy
    • Terms & Conditions

    Resources

    • Blog
    • Brand
    • Help

    Social Links

    • Bluesky
    • Mastodon
    • X
    • Product Hunt
    • GitHub
    • LinkedIn
    • DEV.to
    • YouTube

    © 2026 ToolSura. Free tools that run in your browser.

    Remote-First / Based in India

    Technical Manifesto

    Private • Client-Side • No Uploads

    ToolSura on Nick Launches
    Browser-Native
    Privacy-First
    Skip to main content
    Toolsura
    developer-tools
    A
    Abhay Khant

    Reading a certificate error: string, cause, fix

    October 8, 2026 · 10 min read

    Decode unable to get local issuer certificate, ERR_CERT_AUTHORITY_INVALID and SSL_ERROR_SYSCALL - what each self-signed error means, and the fix.

    Two certificate documents linked by a broken chain, one blue and one grey, beside a red certificate stamped with a warning triangle, next to a browser address bar showing a padlock and a padlocked block.
    Two certificate documents linked by a broken chain, one blue and one grey, beside a red certificate stamped with a warning triangle, next to a browser address bar showing a padlock and a padlocked block.

    Every TLS rejection arrives as a string, and almost every one of those strings is a sentence with a number folded into it. unable to get local issuer certificate. ERR_CERT_AUTHORITY_INVALID. SSL_ERROR_SYSCALL. error 20 at 0 depth lookup. Paste any of them into a general search and you land in a thread where somebody fixed it by adding -k, which is how a broken test environment turns into a broken production one.

    People reach for -k first because the strings themselves are unhelpful. They name a symptom rather than a cause, and different clients describe the same rejection in language so disjoint that the four strings above look like unrelated faults. A browser, an OpenSSL-based CLI and a Go service can all refuse the same certificate and print words that share nothing. What follows is the translation layer: what each string is asserting, which layer produced it, and the change that resolves it.

    Why curl says yes and your browser says no

    The most common report in this family is not that something is broken. It is that curl fetched the page cleanly and the browser refused it, on the same machine, at the same moment. The curl man page explains the split without needing any interpretation. --cacert takes "the specified certificate file to verify the peer", and about the default it says "Normally curl is built to use a default file for this". A file. Chrome consults a certificate store compiled into the browser and then layered with whatever the operating system, or an administrator, has added on top.

    The same page documents the switch, which is the part most people have never found. --ca-native will "Use the operating system's native CA store for certificate verification." That option exists because the two lists diverge on some machines. Where an organisation has pushed a root certificate out through device management or domain policy, Chrome sees it and a stock curl binary does not. The reverse happens on a developer laptop where someone hand-installed a development root into the OS store and never told curl about it. curl also honours the CURL_CA_BUNDLE environment variable when the TLS backend is not Schannel, so even within curl the answer depends on build options and environment.

    A clean curl run against one URL is therefore a statement about one trust list on one machine. It is not a verdict on the certificate, and it predicts nothing about what a user on a different machine will see.

    The decode table: error string, cause, fix

    What you see Layer What it is actually asserting The change that resolves it
    ERR_CERT_AUTHORITY_INVALID (-202) Chromium The signing authority is absent from the store this browser trusts Install the issuing CA as a root in that store
    ERR_CERT_COMMON_NAME_INVALID (-200) Chromium The host you typed is not in the certificate's subjectAltName Reissue with that hostname in a dNSName entry
    ERR_CERT_DATE_INVALID (-201) Chromium Not yet valid, expired, or the client clock is wrong Renew the certificate, or fix the clock
    CERT_REVOKED Chromium The certificate was withdrawn by its issuer Obtain a replacement; reissue does not clear it
    error 20 at 0 depth lookup OpenSSL No issuer was found for the leaf certificate Put the issuer into the trust store
    SSL_ERROR_SYSCALL OpenSSL A fatal I/O error, not necessarily a certificate fault Read errno and the OpenSSL error queue
    x509: certificate signed by unknown authority Go Row one, restated in Go's vocabulary Set RootCAs, or install the CA system-wide
    x509: certificate is valid for example.com, not localhost Go Row two, restated Reissue with the hostname in the SAN
    ssl.SSLCertVerificationError Python A wrapper; the number you want is in verify_code Inspect verify_code before touching anything

    Two rows in that table deserve their own paragraph, because both cost hours when misread.

    Chromium's own comments are the best documentation here, since the browser vendor wrote them, and they are published in the Chromium network error list. The CERT_AUTHORITY_INVALID entry names three possible causes and marks the third as the self-signed case: "The server is presenting a self-signed certificate, providing no defense against active attackers (but foiling passive attackers)." The CERT_COMMON_NAME_INVALID entry lists a different family entirely: a server serving the wrong certificate, a wireless network's login page intercepting the request, a DNS search suffix putting an abbreviated name in the address bar. None of those is an authority problem. Reading the code correctly tells you which of the two you have, and the two fixes do not overlap.

    Then there is SSL_ERROR_SYSCALL, which reads like a network fault and sends people to check firewalls. The SSL_get_error documentation says to consult errno for socket I/O detail, and records a version change worth knowing: before OpenSSL 3.0 an unexpected EOF returned this value with nothing on the error stack and errno of zero. Since 3.0 that case returns SSL_ERROR_SSL with SSL_R_UNEXPECTED_EOF_WHILE_READING on the stack instead.

    So on a current build the string is instructing you to read the error queue, not to look for a bad certificate. On a library pinned before 3.0 it may describe an abruptly dropped connection that never involved a certificate at all.

    "unable to get local issuer certificate": what depth 0 actually means

    The traceback that brings most people here is one line long: ssl.SSLCertVerificationError: unable to get local issuer certificate (_ssl.c:997). Python is not describing your certificate there. It is relaying an OpenSSL error code, and OpenSSL can say considerably more than the relayed string does.

    openssl verify reports the same failure in a form that carries two extra facts. The first line names the certificate and its subject. The second, which OpenSSL documents as the middle line of a three-part diagnostic, "contains the error number and the depth", where "the depth is number of the certificate being verified when a problem was detected starting with zero for the target ("leaf") certificate itself then 1 for the CA that signed the target certificate". Depth 0 therefore means one specific thing: the verifier never located an issuer for the leaf at all. It is not a broken link partway up a chain. There is nothing to repair, and regenerating the certificate will not help.

    What the verifier wanted instead was a decision about trust. OpenSSL frames a successful check as a chain "ending in a certificate that due to some policy is trusted". That final certificate is the trust anchor. RFC 5280 permits a self-signed certificate to be exactly that, and is precise about the mechanics: "When the trust anchor is provided in the form of a self-signed certificate, this self-signed certificate is not included as part of the prospective certification path." It does not need to chain to anything. It needs to be installed. What is an SSL certificate chain covers what the verifier was assembling; the part that matters here is that your certificate is fine and your store is missing an entry.

    Why localhost in the CN still fails

    Generate a certificate for localhost, put localhost in the Common Name field, install it, and Chrome still refuses. The error code is the whole clue: it is ERR_CERT_COMMON_NAME_INVALID, not ERR_CERT_AUTHORITY_INVALID. Nobody in that situation has an authority problem, and anyone who responds by adding the certificate to a trust store will keep failing indefinitely.

    RFC 9525, published in November 2023 and obsoleting RFC 6125, states the rule as a list: "Only check DNS domain names via the subjectAltName extension designed for that purpose: dNSName", and then "Do not include or check strings that look like domain names in the subject's Common Name." The reason it gives is one of types rather than trust. A Common Name RDN "MUST NOT be used to identify a service because it is not strongly typed (it is essentially free-form text) and therefore suffers from ambiguities in interpretation."

    The consequence for a self-signed loopback certificate is mechanical. The hostname must appear in a subjectAltName entry of type dNSName. Leaving it in the Common Name as well costs nothing and helps whoever reads the certificate next; nothing verifies it. Field-by-field guidance is in What is an SSL certificate, but the error code is what this section is about: a name error and an authority error look identical to the person holding the browser and need opposite repairs.

    Why -k is not a fix for unable to get local issuer certificate

    Four settings switch verification off: curl -k, Node's NODE_TLS_REJECT_UNAUTHORIZED=0, rejectUnauthorized: false in a Node HTTP client, and Python's ssl.CERT_NONE. All four work. All four make the message vanish. None of them changes the certificate, and each one has a documented price.

    curl does not merely call it insecure. It names a second-order effect, noting that because a secure connection makes curl "trust responses and allow for example HSTS and Alt-Svc information to be stored and used subsequently", the flag "can make curl trust and use such information from malicious servers." The exposure does not stop at the request you flagged. The Node.js CLI documentation is shorter and just as blunt: disabling validation this way "makes TLS, and HTTPS by extension, insecure", and use of the variable "is strongly discouraged".

    There is a legitimate use, and it is diagnostic. Set the flag once to ask one question: is this purely an authority problem, with the rest of the handshake sound? Then remove it and install the certificate properly. A flag that ships is a certificate nobody validated, on a connection that will also accept an attacker's HSTS header.

    Python deserves a note because its defaults surprise people in the opposite direction. ssl.CERT_NONE is the default verify mode for client sockets "except for PROTOCOL_TLS_CLIENT", a protocol constant that enables CERT_REQUIRED and turns on hostname checking. So a quick script built on a bare socket can accept a self-signed certificate while a hardened context rejects the identical connection, with no change to the server and no change to what the script meant to do.

    Four myths that cost an afternoon

    A self-signed certificate is not broken

    Nothing about it is malformed. It carries a valid signature made with its own key, a validity window, a name that can be entirely correct. RFC 5280 §6.1 explicitly contemplates one being used as a trust anchor, so the standard treats the arrangement as legitimate rather than degenerate. The state you are in is unconfigured, not broken: a certificate that no trust store has been told to accept.

    curl trusting a cert does not mean browsers will

    A passing curl verifies a file. Chrome consults a store. On any machine carrying an administrator-installed root the two lists differ and both verdicts are correct. The corollary runs the other way and costs more: a bypass pinned into a build script means every user outside your own machine meets the failure you were hiding from yourself.

    The padlock is not a trustworthiness score

    A padlock reports exactly one thing, that a certificate was accepted against the store that browser trusts. MDN defines a secure context as "a Window or Worker in which certain minimum standards of authentication and confidentiality are met": a statement about transport, not about whether the site is honest, whether it is a phishing clone, or what it intends to do with a card number.

    -k is not a fix

    It changes nothing about the certificate and silences the only tool that was telling you the certificate had a problem. The cached HSTS and Alt-Svc behaviour curl warns about is the reason it compounds. Treat the flag as an instrument for proving one hypothesis, then delete it.

    Where each stack keeps its trust list

    The same certificate, a different store in every tool. Each one below has a correct configuration and a bypass, and they do not read from the same file.

    Git exposes three separate knobs. http.sslCAInfo is a "File containing the certificates to verify the peer with when fetching or pushing over HTTPS", overridable through GIT_SSL_CAINFO. http.sslCAPath takes a directory of certificates. http.sslVerify turns checking off, and the git-config documentation takes the trouble to show it scoped per URL with --url=, which is worth reading closely, since a blanket false in global config is how an internal host quietly stops verifying anything.

    Node.js bundles its own CA list. NODE_EXTRA_CA_CERTS adds a file, and NODE_USE_SYSTEM_CA=1 brings in the system store "along with" the bundled option; it arrived in v22.19.0 and is the documented answer to making Node see a development CA. NODE_TLS_REJECT_UNAUTHORIZED=0 is the bypass beside it, not the fix.

    Python has three independent switches: verify mode, hostname checking, and which file is loaded. That is why fixing one and expecting the failure to disappear often does nothing. The module documentation for ssl documents VERIFY_X509_PARTIAL_CHAIN, the option most readers have never met; it instructs OpenSSL "to accept intermediate CAs in the trust store to be treated as trust-anchors, in the same way as the self-signed root CA certificates", which is the right answer when your development CA signs leaf certificates itself.

    Go makes the two authority failures checkable types instead of strings, so the idiomatic handling is errors.As against UnknownAuthorityError or HostnameError, not a comparison against message text. Both types are defined in Go's certificate verification source.

    Java keeps the same decision in a truststore, and the JSSE reference guide is blunt about what it represents: "A trust manager decides who to trust based on information in the truststore it manages." An entry in that store is a decision about a certificate, rather than a property of one, which is the sentence you could write about every tool on this list.

    Related Tools & Further Reading

    • SSL Checker — once you know which error you are holding, inspect what the server actually sent: issuer chain, subjectAltName entries, validity dates.
    • What is an SSL certificate chain? — the structure a verifier was assembling when it reported error 20.
    • What is an SSL certificate? — field-level background for the name and validity checks behind rows two and three.
    • RFC 9525, Service Identity in TLS — the current normative source on hostname matching, and the reason the Common Name stopped working.
    A

    Written by

    Abhay Khant

    Abhay Khant is the founder of ToolSura, a privacy-first developer tools platform. Writes about client-side architecture, AI tooling, and the open web.

    Share

    Frequently Asked Questions

    PreviousSHA256 Password Hashing: Why Speed Is the ProblemNextCORS Preflight Failed: Causes, Fixes, and Four Myths

    Comments

    Leave a Review

    Rate this tool
    Overall Rating
    Spam Protection Active