Go — x509 certificate signed by unknown authority
`x509: certificate signed by unknown authority` means Go could not find a trusted root for the certificate a server presented. Unlike Python, Go does read the operating system trust store, so the cause is nearly always that there is no trust store to read — a container built `FROM scratch` or a bare `alpine` image ships no CA certificates at all. Install the `ca-certificates` package, or copy `/etc/ssl/certs/ca-certificates.crt` into the image. If the chain ends at a corporate root, install that root instead.
Step 1 — does the image have a trust store? #
docker run --rm your-image ls -la /etc/ssl/certs/ca-certificates.crt No such file or directory is the answer in most cases. A scratch image contains only your
binary; a bare alpine ships without CA certificates too.
Fixing it in a Dockerfile #
RUN apk add --no-cache ca-certificates RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/* Copy the bundle from a builder stage — there is no package manager to install it with:
FROM golang:1.23-alpine AS build
RUN apk add --no-cache ca-certificates
COPY . /src
WORKDIR /src
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"] gcr.io/distroless/static already includes a CA bundle. If you are seeing this error on
distroless, the cause is a corporate root rather than a missing store — see below.
A corporate root #
If the chain ends at a root your organisation controls, the bundle exists but does not contain it. Find out what the top of the chain actually is:
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
| grep -E "^ *[0-9]+ s:" Then add it:
COPY corporate-root.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates Or without rebuilding, by pointing Go at a bundle:
export SSL_CERT_FILE=/etc/ssl/certs/corporate-bundle.crt A missing intermediate #
Less common with Go, but check it before blaming the client. If the server sends only its own certificate, the chain is incomplete and every non-browser client fails:
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
| grep -c "BEGIN CERTIFICATE" Expected output
2
A count of 1 from a publicly trusted site means the server is at fault — see unable to get local issuer certificate.
Supplying a root in code #
When one client needs a root the rest of the system should not trust:
pool, err := x509.SystemCertPool()
if err != nil {
pool = x509.NewCertPool()
}
pem, err := os.ReadFile("corporate-root.crt")
if err != nil {
return err
}
if !pool.AppendCertsFromPEM(pem) {
return errors.New("no certificates found in corporate-root.crt")
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool},
},
} Starting from SystemCertPool keeps every normal root working; starting from an empty pool would
trust only the corporate root and break everything else.
Frequently asked questions
- Why does this only happen in my container?
- Because your development machine has a populated trust store and the image does not. Minimal base images ship no CA certificates, so every TLS connection from inside them fails until you add them.
- Does Go use the system trust store?
- Yes, on every platform. That is why this error usually points at the environment rather than the code.
SSL_CERT_FILEandSSL_CERT_DIRoverride it if you need a specific bundle. - Can I skip verification for testing?
- Setting
InsecureSkipVerifyon atls.Configdoes it, and it disables authentication entirely for that client. It is a diagnostic, not a fix, and it should never reach production code. - How do I add a root for a Go program without rebuilding the image?
- Set
SSL_CERT_FILEto a bundle containing it. Go reads that variable, so a mounted file plus an environment variable is enough.
References
- Go crypto/x509 — how Go builds and verifies a chain, and where it looks for roots
- RFC 5280: X.509 and CRL profile — the specification every field on a certificate comes from