caddyserver/caddy · error

reading %s: %v

Error message

reading %s: %v

What it means

FileCAPool.Provision reads each file listed in trusted_ca_certs_pem_files with os.ReadFile; when the OS returns an error the wrapper reports the path and cause. This is a local filesystem problem before any parsing happens: the file does not exist, the caddy process lacks read permission, or the path is a directory.

Source

Thrown at modules/caddytls/capools.go:153

// CaddyModule implements caddy.Module.
func (FileCAPool) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID: "tls.ca_pool.source.file",
		New: func() caddy.Module {
			return new(FileCAPool)
		},
	}
}

// Loads and decodes the DER and pem files to generate the certificate pool
func (f *FileCAPool) Provision(ctx caddy.Context) error {
	caPool := x509.NewCertPool()
	var certs []*x509.Certificate
	for _, pemFile := range f.TrustedCACertPEMFiles {
		pemContents, err := os.ReadFile(pemFile)
		if err != nil {
			return fmt.Errorf("reading %s: %v", pemFile, err)
		}
		// Parse PEM to extract certificates
		for len(pemContents) > 0 {
			var block *pem.Block
			block, pemContents = pem.Decode(pemContents)
			if block == nil {
				break
			}
			if block.Type != "CERTIFICATE" {
				continue
			}
			cert, err := x509.ParseCertificate(block.Bytes)
			if err != nil {
				return fmt.Errorf("parsing certificate in %s: %v", pemFile, err)
			}
			caPool.AddCert(cert)
			certs = append(certs, cert)
		}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Confirm the file exists at the exact path in the error (ls -l), checking for typos.
  2. Grant read access: chmod o+r ca.pem or chown to the caddy user; for systemd, ensure the file is readable by User=caddy.
  3. Use absolute paths, and in containers verify the volume mount actually places the file at that path.
  4. If the message says 'is a directory', point to the .pem file itself, not its folder.

Example fix

# before
sudo chmod 600 /etc/caddy/ca.pem  # caddy cannot read
client_auth {
	trusted_ca_cert_file /etc/caddy/ca.pem
}

# after
sudo chmod 644 /etc/caddy/ca.pem
client_auth {
	trusted_ca_cert_file /etc/caddy/ca.pem
}
Defensive patterns

Strategy: validation

Validate before calling

for _, f := range poolCfg.TrustedCACertPEMFiles {
    info, err := os.Stat(f)
    if err != nil {
        return fmt.Errorf("CA file %s: %v", f, err)
    }
    if info.IsDir() || info.Mode().Perm()&0o004 == 0 {
        return fmt.Errorf("CA file %s must be a readable regular file", f)
    }
}

Prevention

When it happens

Trigger: Pointing trusted_ca_certs_pem_files (Caddyfile: trusted_ca_cert_file) at a nonexistent or misspelled path, a path not readable by the caddy service user, or a relative path resolved against a different working directory (notably with systemd or containers).

Common situations: Certificates stored under /root readable only by root while Caddy runs as caddy; Docker deployments where the CA file was not mounted into the container; paths with typos or missing directory components.

Related errors


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