caddyserver/caddy · error
source module is not a CA pool provider
Error message
source module is not a CA pool provider
What it means
A module loaded as a source inside a combined CA pool did not type-assert to the CA interface (which requires CertPool() *x509.CertPool). Everything in the tls.ca_pool.source namespace should implement CA, so this fires when a non-CA module ends up in the sources list.
Source
Thrown at modules/caddytls/capools.go:867
// Provision implements caddy.Provisioner.
func (ccp *CombinedCAPool) Provision(ctx caddy.Context) error {
if len(ccp.SourcesRaw) == 0 {
return fmt.Errorf("no sources specified for combined CA pool")
}
// Load all source modules
sources, err := ctx.LoadModule(ccp, "SourcesRaw")
if err != nil {
return fmt.Errorf("loading CA pool sources: %v", err)
}
caPool := x509.NewCertPool()
var allCerts []*x509.Certificate
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)View on GitHub (pinned to 50e54ee279)
Solutions
- Use only modules whose ID starts with tls.ca_pool.source. in the combined pool's sources.
- Prefer authoring via Caddyfile so the adapter only emits valid source modules.
- If writing a custom pool module, ensure it implements CertPool() and is registered under the correct namespace.
Example fix
// before (JSON)
{"source": {"module": "caddy.storage.file_system"}}
// after (JSON)
{"source": {"module": "tls.ca_pool.source.file", "pem_files": ["/etc/caddy/roots.pem"]}} Defensive patterns
Strategy: type-guard
Type guard
// guard custom module wiring: only CA pool sources may be combined
func isCAPoolSource(mod caddy.Module) bool {
_, ok := mod.(caddytls.CA)
return ok
}
// stricter: also require CertificateProvider for combined pools
func combinable(mod caddy.Module) bool {
ca, ok := mod.(caddytls.CA)
if !ok {
return false
}
_, ok = ca.(caddytls.CertificateProvider)
return ok
} Prevention
- Author trust pools via Caddyfile so the adapter only emits valid tls.ca_pool.source modules.
- For JSON tooling, whitelist the tls.ca_pool.source namespace.
- When writing custom pool modules, register under tls.ca_pool.source.<name> and implement CA.
When it happens
Trigger: Programmatically or via hand-written JSON placing a module that is not a CA pool source (e.g. a storage module or arbitrary module ID) into the combined pool's sources array. The Caddyfile adapter normally prevents this.
Common situations: Hand-crafted JSON configs; tooling that injects raw module objects; experimental modules that register under the wrong namespace.
Related errors
- module is not a request matcher: %T
- no sources specified for combined CA pool
- loading CA pool sources: %v
- source %T does not implement CertificateProvider (required f
- source %T returned nil certificates
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/9f7dc445c43bb7a0.
Report an issue: GitHub.