Error message
tls: failed to verify certificate: x509: certificate signed by unknown authority
Go crypto/tls (Go ≥ 1.20) — wrapping x509 verification errors
What it means
Since Go 1.20, TLS handshake failures wrap the underlying x509 error with a "tls: failed to verify certificate:" prefix, so logs show both the layer (TLS handshake) and the root cause (chain building failed). The suffix varies — unknown authority, expired, hostname mismatch — and is the part to act on. The prefix is also a handy log filter: grepping for it separates TLS trust failures from application-level errors, which is exactly why the wrapping was added.
Why it happens
Any of the unknown-authority causes
commonMissing intermediates, private CAs, and CA-less containers — everything that produces "x509: certificate signed by unknown authority" now appears with this prefix when it happens inside a TLS dial rather than an explicit Verify call.
Dev/self-signed endpoints hit by default clients
commonhttp.Get against a local server with a self-signed certificate (httptest.NewTLSServer from another process, minikube ingress, a dev proxy) fails here unless the client is given the server's CA.
The suffix differs — read it
occasional"...: x509: certificate has expired" or "...: x509: certificate is valid for X, not Y" are different problems wearing the same prefix; the fix lives with the suffix, not the TLS layer.
How to fix it
- 1.
Give the client the right trust anchors
Same remedy as the bare x509 error: a root pool containing the issuing CA, set on the transport actually used for the dial.
go pool := x509.NewCertPool() caPEM, _ := os.ReadFile("ca.pem") pool.AppendCertsFromPEM(caPEM) client := &http.Client{Transport: &http.Transport{ TLSClientConfig: &tls.Config{RootCAs: pool}, }} resp, err := client.Get("https://internal.example:8443/healthz") - 2.
Inspect what the server presents
See the served chain, its issuers and validity before touching client code — most fixes turn out to be server-side chain configuration.
bash openssl s_client -connect internal.example:8443 -servername internal.example \ </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates - 3.
Keep verification on, even in tests
httptest servers hand you a pre-configured client; use it instead of disabling verification globally.
go srv := httptest.NewTLSServer(handler) defer srv.Close() resp, err := srv.Client().Get(srv.URL) // trusts srv's cert, nothing else