Programming languages
x509: certificate signed by unknown authority (Go): fixes
x509: certificate signed by unknown authorityx509: certificate signed by unknown authority is Go's way of saying the server's certificate chain does not end at any root certificate the program trusts. Usually the server is missing an intermediate, the machine or container lacks a CA bundle, or the server uses a private CA (or a TLS-inspecting proxy) whose root was never installed.
Quick fix
What the server sends decides the fix: repair its chain, install a CA bundle, or trust the private root.
First check what the server sends and who signed it:
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null | grep -E 'depth|s:|i:|Verify return'
- Only a leaf,
Verify return code: 21: the server chain is incomplete. Fix the server (see missing intermediate certificate). - Full chain to a public root, but your program still fails: the client has no or an old CA bundle. Install
ca-certificates. - Chain ends at a private or proxy root: trust that root on the client:
export SSL_CERT_FILE=/etc/ssl/certs/company-bundle.pem # Linux, per process
Better, add the root to the system store (commands below) so every Go tool picks it up.
Causes
1. No CA bundle in the container
Minimal images (scratch, some distroless and slim variants) ship without CA certificates. Every HTTPS call from a Go binary then fails with this error.
- Confirm:
ls /etc/ssl/certs/inside the container is empty or missing. - Fix (multi-stage build):
FROM golang:1 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app .
FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /app /app
ENTRYPOINT ["/app"]
Alternatively, import _ "golang.org/x/crypto/x509roots/fallback" embeds a Mozilla-derived root bundle that Go uses only when no system roots are found.
2. The server omits its intermediate certificate
Go does not fetch missing intermediates. A server that sends only the leaf works in Chrome and fails in Go.
- Fix: configure the full chain on the server; check it with the chain builder.
3. Private CA, self-signed certificate or proxy root
Internal registries, Vault, Consul, Kubernetes API servers and corporate proxies all use roots that are not in public bundles.
- Fix in code, keeping the system roots and adding yours:
pool, err := x509.SystemCertPool()
if err != nil {
pool = x509.NewCertPool()
}
pem, err := os.ReadFile("/etc/myapp/company-root.pem")
if err != nil {
log.Fatal(err)
}
if !pool.AppendCertsFromPEM(pem) {
log.Fatal("no certificates parsed from company-root.pem")
}
client := &http.Client{Transport: &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool},
}}
4. Wrong root after a CA change
If a CA moved to a new root and your bundle predates it, chains to the new root fail. Updating the ca-certificates package fixes it.
Fixes by environment
Every fix installs the missing root where the Go program looks: the system store, the container image, or RootCAs in code.
Debian / Ubuntu:
sudo apt-get install -y ca-certificates
sudo cp company-root.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
RHEL / Rocky / Fedora: copy to /etc/pki/ca-trust/source/anchors/ and run sudo update-ca-trust.
Alpine: apk add --no-cache ca-certificates, then copy the root into /usr/local/share/ca-certificates/ and run update-ca-certificates.
macOS and Windows: Go uses the platform verifier, so add the root to the System keychain ("Always Trust") or the Windows "Trusted Root Certification Authorities" store.
Tool-specific pages: Docker pulls are verified by the daemon, not the CLI, and kubectl uses the CA from kubeconfig; see Docker x509 error and kubectl x509 error.
Go module downloads: go mod download behind a TLS-inspecting proxy fails the same way. Trust the proxy root system-wide rather than setting GOINSECURE.
Don't do this
InsecureSkipVerify: true. It disables both chain and hostname checks. Linters such as gosec flag it.- Replacing
RootCAswith only your private root when the program also talks to public services. Start fromx509.SystemCertPool()and append. GOINSECUREto get past module download errors. It skips HTTPS certificate checks for every matching module path, so a tampered download goes unnoticed unless the checksum database catches it.- Copying a random
ca-certificates.crtfrom the internet into your image. Use the distribution package or the Go fallback package.
FAQ
Where does Go look for trusted root certificates?
On Linux and most Unix systems Go reads well-known bundle files such as /etc/ssl/certs/ca-certificates.crt and /etc/pki/tls/certs/ca-bundle.crt, and the SSL_CERT_FILE and SSL_CERT_DIR environment variables override them. On Windows and macOS Go asks the operating system's own verifier, so roots in the Windows store or the macOS keychain are trusted.
My Go binary works on my laptop but fails in a scratch or distroless container. Why?
A FROM scratch image contains only your binary, so there is no CA bundle for Go to read. Copy /etc/ssl/certs/ca-certificates.crt from the build stage, or embed a fallback bundle with the golang.org/x/crypto/x509roots/fallback package.
Is InsecureSkipVerify ever acceptable?
Not in code that ships. InsecureSkipVerify: true skips both chain and hostname verification, so any machine on the path can impersonate the server. If you need to trust a private CA, put that CA in RootCAs instead.
The message now starts with "tls: failed to verify certificate". Is it a different error?
No. Newer Go versions wrap the same x509 error with that prefix. The cause and the fixes are the same.