caddyserver/caddy · error

source %T returned nil certificates

Error message

source %T returned nil certificates

What it means

A source inside a combined CA pool implemented CertificateProvider but its Certificates() call returned a nil slice. The combined pool treats nil (as opposed to an empty non-nil slice) as a defective source and refuses to continue provisioning.

Source

Thrown at modules/caddytls/capools.go:878

	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)
		}
	}

	ccp.pool = caPool
	ccp.certs = allCerts

	return nil
}

// Syntax:
//

View on GitHub (pinned to 50e54ee279)

Solutions

  1. In the custom module, always initialize the certificates slice during Provision even when empty: `certs := []*x509.Certificate{}`.
  2. Ensure Certificates() uses a pointer receiver over the same state that Provision populated.
  3. Add a unit test asserting Certificates() is non-nil after Provision.

Example fix

// before
func (m *MyPool) Provision(ctx caddy.Context) error {
	m.pool = x509.NewCertPool()
	return nil
}

// after
func (m *MyPool) Provision(ctx caddy.Context) error {
	m.pool = x509.NewCertPool()
	m.certs = []*x509.Certificate{}
	return nil
}
Defensive patterns

Strategy: validation

Validate before calling

// custom modules: always initialize the certs slice in Provision
func (m *MyPool) Provision(ctx caddy.Context) error {
	m.pool = x509.NewCertPool()
	m.certs = []*x509.Certificate{} // non-nil, even when empty
	return nil
}

Prevention

When it happens

Trigger: A custom source module whose Certificates() returns nil because certificates were not stored during its Provision (e.g. field never populated on an error path, or value-receiver returning an unset field).

Common situations: Custom pool modules that lazily build the pool but forget to populate the certs slice; copy-paste module skeletons; receiver-type mistakes (value receiver on pointer-populated state).

Understand the failure class

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/7d9b5f8c0feac358. Report an issue: GitHub.