caddyserver/caddy · error
source %T returned a nil certificate
Error message
source %T returned a nil certificate
What it means
Thrown while provisioning a 'combined' CA trust pool (tls.ca_pool.source.combined): one of the configured source modules returned a slice of certificates that contains a nil *x509.Certificate element. Caddy refuses to add nil certs to the x509.CertPool because a nil certificate cannot be parsed and would silently corrupt trust evaluation. The %T verb prints the offending source module's Go type, which identifies which source is broken.
Source
Thrown at modules/caddytls/capools.go:882
for _, src := range sources.([]any) {
ca, ok := src.(CA)
if !ok {
return fmt.Errorf("source module is not a CA pool provider")
}
ccp.sources = append(ccp.sources, ca)
certProvider, ok := ca.(CertificateProvider)
if !ok {
return fmt.Errorf("source %T does not implement CertificateProvider (required for combining)", ca)
}
certs := certProvider.Certificates()
if certs == nil {
return fmt.Errorf("source %T returned nil certificates", ca)
}
for _, cert := range certs {
if cert == nil {
return fmt.Errorf("source %T returned a nil certificate", ca)
}
caPool.AddCert(cert)
allCerts = append(allCerts, cert)
}
}
ccp.pool = caPool
ccp.certs = allCerts
return nil
}
// Syntax:
//
// trust_pool combined {
// source <module_name> {
// <module_config>
// }View on GitHub (pinned to 50e54ee279)
Solutions
- Read the %T type name in the error to identify which source module is at fault
- Fix that module's Certificates() so it never returns nil elements: skip the entry, or return an error explaining why the certificate could not be loaded
- Verify the certificate files/inputs feeding that source are valid and readable
- If the module is third-party, report the bug upstream and pin a working version
Example fix
// before (inside a custom source's Certificates())
certs := make([]*x509.Certificate, len(files))
for i, f := range files {
der, _ := os.ReadFile(f) // error ignored
cert, _ := x509.ParseCertificate(der)
certs[i] = cert // can be nil
}
return certs
// after
var certs []*x509.Certificate
for _, f := range files {
der, err := os.ReadFile(f)
if err != nil {
return nil, fmt.Errorf("reading %s: %v", f, err)
}
cert, err := x509.ParseCertificate(der)
if err != nil {
return nil, fmt.Errorf("parsing %s: %v", f, err)
}
certs = append(certs, cert)
}
return certs Defensive patterns
Strategy: validation
Validate before calling
// Before returning certs from a custom CA source, self-check:
func validateCerts(certs []*x509.Certificate) error {
for i, c := range certs {
if c == nil {
return fmt.Errorf("certificate at index %d is nil", i)
}
}
return nil
} Prevention
- When implementing CertificateProvider, never append nil on parse failure — return an error instead
- Avoid make([]*x509.Certificate, n) + index assignment; use append so unfilled slots cannot exist
- Test custom CA sources with an empty and a partially-invalid input directory before shipping
- Keep combined trust pool configs small so the %T in the error is easy to map to one source
When it happens
Trigger: Provisioning a CombinedCertPool whose SourcesRaw includes a custom CA source module (or a third-party plugin) whose Certificates() implementation returns a slice like []*x509.Certificate{validCert, nil}. Also reachable if a source builds its slice with make([]*x509.Certificate, n) and fails to fill every index, or appends a nil on a parse failure instead of returning an error.
Common situations: A plugin implementing the CA/CertificateProvider interfaces with sloppy slice handling; a source that reads a directory of DER files, skips unreadable ones by appending nil, and the operator points it at a partially-readable directory.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- trust_pool module '%s' is not a certificate pool provider
- handshake context: %v
- provisioning client CA: %v
- consolidating TLS connection policies for server %d: %v
- server listening on %v is HTTP, but attempts to configure TL
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/8e2f5f4c4dc59683.
Report an issue: GitHub.