caddyserver/caddy · error

HTTP %d fetching CA certificate bundle from %s

Error message

HTTP %d fetching CA certificate bundle from %s

What it means

The `tls.ca_pool.source.http` (HTTPCertPool) module fetched the configured URL to retrieve a CA bundle, and the response status was outside 2xx. The bundle cannot be trusted or parsed from an unsuccessful response.

Source

Thrown at modules/caddytls/capools.go:691

	httpClient := *http.DefaultClient
	httpClient.Transport = customTransport

	for _, uri := range hcp.Endpoints {
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
		if err != nil {
			return err
		}
		res, err := httpClient.Do(req) //nolint:gosec // SSRF false positive... uri comes from config
		if err != nil {
			return err
		}
		pembs, err := io.ReadAll(res.Body)
		res.Body.Close()
		if err != nil {
			return err
		}
		if res.StatusCode < 200 || res.StatusCode >= 300 {
			return fmt.Errorf("HTTP %d fetching CA certificate bundle from %s", res.StatusCode, uri)
		}
		// Parse PEM to extract certificates
		pemData := pembs
		for len(pemData) > 0 {
			var block *pem.Block
			block, pemData = pem.Decode(pemData)
			if block == nil {
				break
			}
			if block.Type != "CERTIFICATE" {
				continue
			}
			cert, err := x509.ParseCertificate(block.Bytes)
			if err != nil {
				return fmt.Errorf("parsing certificate from URL %s: %v", uri, err)
			}
			caPool.AddCert(cert)
			certs = append(certs, cert)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. curl -I the exact configured URL from the Caddy host and confirm it returns 2xx with the PEM bundle.
  2. Fix the path/host or move the bundle to a reachable unauthenticated URL.
  3. If the failure is transient, simply reload/retry after the endpoint recovers; consider serving the bundle locally (file pool) if the remote is unreliable.

Example fix

# before
trust_pool http https://ca.internal/roots/bundle-crashed.pem

# after
trust_pool http https://ca.internal/roots/bundle.pem   # verified with curl -I -> 200
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight the bundle URL from the Caddy host
import "net/http"

func bundleURLReachable(u string) error {
	resp, err := http.Head(u) //nolint:gosec // operator-supplied URL
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("bundle URL returned HTTP %d", resp.StatusCode)
	}
	return nil
}

Try / catch

// on reload failure due to transient upstream errors, keep previous config and retry the reload later
if err := applyConfig(cfg); err != nil && strings.Contains(err.Error(), "fetching CA certificate bundle") {
	scheduleRetryReload(backoff) // previous good config stays active
}

Prevention

When it happens

Trigger: The URL returns 404 (wrong path), 401/403 (auth required), 5xx (server error), or a redirect chain ending in a non-2xx — any status <200 or >=299 triggers this.

Common situations: Bundle moved or URL typo'd; the endpoint requires authentication headers the module does not send; transient upstream outages; rate-limiting (429) from public bundle hosts.

Understand the failure class

Related errors


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