# 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.

Source: https://certguru.io/errors/x509-signed-by-unknown-authority-go/
Last updated: 2026-08-19
Commands verified against OpenSSL 3.5.7.

---

> **What it looks like**
>
> ```
>   Get "https://example.com": x509: certificate signed by unknown authority
>   ```
>   From Docker: `x509: certificate signed by unknown authority` when pulling from a registry.

## Step 1 — does the image have a trust store?

```bash
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

#### Alpine

```dockerfile
RUN apk add --no-cache ca-certificates
```

#### Debian / Ubuntu

```dockerfile
RUN apt-get update \
 && apt-get install -y --no-install-recommends ca-certificates \
 && rm -rf /var/lib/apt/lists/*
```

#### scratch

Copy the bundle from a builder stage — there is no package manager to install it with:

    ```dockerfile
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"]
```

#### distroless

`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:

```bash
openssl s_client -connect example.com:443 -servername example.com -showcerts /dev/null \
  | grep -E "^ *[0-9]+ s:"
```

Then add it:

```dockerfile
COPY corporate-root.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
```

Or without rebuilding, by pointing Go at a bundle:

```bash
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:

```bash
openssl s_client -connect example.com:443 -servername example.com -showcerts /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](/errors/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:

```go
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.

> **InsecureSkipVerify is not the fix**
>
> `tls.Config{InsecureSkipVerify: true}` makes the error disappear by removing authentication
>   altogether. The connection is encrypted and anyone on the path can still impersonate the server.


## 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_FILE` and `SSL_CERT_DIR` override it if you need a specific bundle.

### Can I skip verification for testing?

Setting `InsecureSkipVerify` on a `tls.Config` does 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_FILE` to a bundle containing it. Go reads that variable, so a mounted file plus an environment variable is enough.


## References

- [Go crypto/x509](https://pkg.go.dev/crypto/x509) — how Go builds and verifies a chain, and where it looks for roots
- [RFC 5280: X.509 and CRL profile](https://www.rfc-editor.org/rfc/rfc5280) — the specification every field on a certificate comes from

## Related pages

- https://certguru.io/errors/self-signed-certificate-in-chain/
- https://certguru.io/errors/unable-to-get-local-issuer-certificate/
- https://certguru.io/errors/pkix-path-building-failed/
- https://certguru.io/concepts/certificate-chain/
- https://certguru.io/platforms/kubernetes-tls-secret/
- https://certguru.io/openssl/get-certificate-from-server/

--
CertGuru — https://certguru.io
