JuliusBrussee/caveman · error
%s (%s) contains no valid PEM certificate
Error message
%s (%s) contains no valid PEM certificate
What it means
Thrown by rootsWithCAFile (chhttp.go:191) when the PEM decode loop finished without adding a single certificate: the file decoded, contained no unparseable CERTIFICATE blocks, but also zero CERTIFICATE-type blocks. Typical content is a PRIVATE KEY block, a CSR, or some other PEM/non-PEM payload. Setting a CA-file env var to such a file is treated as a configuration error, not an empty success.
Source
Thrown at shared/platform/chhttp/chhttp.go:191
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++
}
if bytes.Contains(rest, []byte("-----BEGIN")) {
return nil, fmt.Errorf("%s (%s): trailing PEM block is truncated after %d certificate(s), so the bundle is incomplete and must not be half-trusted", caFileEnv, path, added)
}
if added == 0 {
return nil, fmt.Errorf("%s (%s) contains no valid PEM certificate", caFileEnv, path)
}
return roots, nil
}
// errTransport fails every request with the configuration error that produced
// it. A client constructor cannot return an error, and falling back to the
// default transport would silently trade a rejected TLS configuration for
// unpinned verification — so the client is built, and refuses to send.
type errTransport struct{ err error }
func (t errTransport) RoundTrip(*http.Request) (*http.Response, error) { return nil, t.err }
// queryTransport is the shared connection pool behind ClickHouse query clients.
//
// http.DefaultTransport keeps only DefaultMaxIdleConnsPerHost (2) idle
// connections per host, so the 3rd+ concurrent ClickHouse read dials a fresh
// connection and throws it away on completion — paying a TCP + TLS handshake per
// query. Report handlers fan several queries out at once and production readsView on GitHub (pinned to 27d5a3981a)
Solutions
- Confirm the file actually contains -----BEGIN CERTIFICATE----- blocks: grep 'BEGIN CERTIFICATE' <file>.
- If the material is PKCS#12, convert it first: openssl pkcs12 -in store.p12 -nokeys -out bundle.pem.
- If it is a DER certificate, convert: openssl x509 -inform der -in ca.der -out bundle.pem.
- Point the env var at the CA certificate chain, never at the private key.
Example fix
# before export CLICKHOUSE_CA_FILE=/certs/tls.key # wrong artifact # after export CLICKHOUSE_CA_FILE=/certs/ca-bundle.pem # contains CERTIFICATE blocks
Defensive patterns
Strategy: validation
Validate before calling
data, _ := os.ReadFile(path)
if !bytes.Contains(data, []byte("-----BEGIN CERTIFICATE-----")) {
return fmt.Errorf("%s contains no PEM certificate; not a CA bundle", path)
} Try / catch
if err := validateBundleHasCerts(path); err != nil {
return err // do not default to an empty pool silently
} Prevention
- Name CA bundles explicitly (ca-bundle.pem) and keys separately (tls.key) to avoid swaps.
- Convert PKCS#12/DER to PEM at packaging time, not at runtime.
- Assert 'BEGIN CERTIFICATE' appears in the file in your deploy scripts.
When it happens
Trigger: Pointing the CA-file env var at the wrong artifact: a TLS private key (-----BEGIN PRIVATE KEY-----), a certificate signing request, a keystore in another format (PKCS#12/.p12, DER binary), or a documentation file. All non-CERTIFICATE blocks are skipped by the loop, added stays 0, and this error returns.
Common situations: Confusing tls.key/tls.crt during setup; putting a .p12 file where PEM was expected; leaving a placeholder file from an install template; generating a key but never exporting the certificate.
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: load system certificate pool: %w
- %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/c09d8405958d72ca.
Report an issue: GitHub.