JuliusBrussee/caveman · error
%s: load system certificate pool: %w
Error message
%s: load system certificate pool: %w
What it means
Thrown by rootsWithCAFile in shared/platform/chhttp/chhttp.go:167 after the custom CA bundle file was read successfully but x509.SystemCertPool() failed. The function builds a trust pool by starting from the OS trust store and appending the bundle's certificates, so the system pool is a hard prerequisite. The error is prefixed with the CA-file environment variable name so the operator knows which knob produced it.
Source
Thrown at shared/platform/chhttp/chhttp.go:167
// Appending (rather than replacing) keeps a public managed endpoint verifiable
// while a private CA is trusted for the internal one.
//
// The bundle is parsed block by block instead of via CertPool.AppendCertsFromPEM,
// which reports success as soon as ONE certificate parses and silently drops the
// rest. A secret mount that is truncated mid-bundle, or corrupt after the first
// entry, would then be half-trusted: the endpoints whose issuer survived keep
// verifying and the ones whose issuer was dropped fail later, at the first
// telemetry flush, looking like a network fault. Any unusable certificate block —
// or a trailing PEM header with no complete block behind it — fails the whole
// bundle CLOSED at boot instead.
func rootsWithCAFile(path string) (*x509.CertPool, error) {
bundle, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("%s: %w", caFileEnv, err)
}
roots, err := x509.SystemCertPool()
if err != nil {
return nil, fmt.Errorf("%s: load system certificate pool: %w", caFileEnv, err)
}
added := 0
rest := bundle
for {
var block *pem.Block
block, rest = pem.Decode(rest)
if block == nil {
break
}
if block.Type != "CERTIFICATE" {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%s (%s): certificate %d is unparseable, so the bundle is incomplete and must not be half-trusted: %w", caFileEnv, path, added+1, err)
}
roots.AddCert(cert)
added++View on GitHub (pinned to 27d5a3981a)
Solutions
- Install the OS certificate package in the image (apk add ca-certificates / apt-get install ca-certificates) or copy /etc/ssl/certs from a builder stage.
- Check SSL_CERT_FILE and SSL_CERT_DIR in the process environment and make sure they point to existing, readable files/directories.
- Verify the service user has read permission on the system trust store.
- As a last resort on scratch images, set SSL_CERT_FILE to the custom bundle itself so SystemCertPool reads a file that exists.
Example fix
# before (Dockerfile) FROM scratch COPY myapp /myapp # after FROM golang:1.23 AS build RUN go build -o /myapp ./... FROM alpine:3.20 RUN apk add --no-cache ca-certificates COPY --from=build /myapp /myapp
Defensive patterns
Strategy: validation
Validate before calling
// before setting the CA-file env var, prove the system pool loads
if _, err := x509.SystemCertPool(); err != nil {
log.Fatalf("system trust store unusable: %v (check SSL_CERT_FILE/SSL_CERT_DIR and image certs)", err)
} Try / catch
roots, err := chhttpTrustPool(path)
if err != nil {
// fail closed: do not fall back to a default transport
return fmt.Errorf("boot: TLS trust config: %w", err)
} Prevention
- Install ca-certificates in every container image that runs the service.
- Add a boot-time smoke test that loads the system pool in the deployed image, not just on dev machines.
- Keep SSL_CERT_FILE/SSL_CERT_DIR pointing at existing files in production manifests.
When it happens
Trigger: Setting the CA-file env var (caFileEnv, e.g. CLICKHOUSE_CA_FILE) to a readable file while the process cannot load the operating system trust store: SSL_CERT_FILE or SSL_CERT_DIR points to a missing/unreadable file, or the container image ships no /etc/ssl/certs at all (scratch/distroless without ca-certificates).
Common situations: Minimal Docker images (FROM scratch or distroless) that never installed the ca-certificates package; a stray SSL_CERT_FILE env var left pointing at a file deleted during image slimming; hardened hosts where the trust store is not readable by the service user.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- %s (%s): certificate %d is unparseable, so the bundle is inc
- %s (%s) contains no valid PEM certificate
- %s (%s): trailing PEM block is truncated after %d certificat
- production requires an https:// CLICKHOUSE_URL (TLS only); C
- invalid listen address %q: %w
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/4956a7b724305cd3.
Report an issue: GitHub.