{"record":{"id":"913f0043cace474a","repo":"caddyserver/caddy","slug":"ca-id-is-required-use-local-for-the-default-ca","errorCode":null,"errorMessage":"CA ID is required (use 'local' for the default CA)","messagePattern":"CA ID is required \\(use 'local' for the default CA\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddypki/ca.go","lineNumber":105,"sourceCode":"\tstorage    certmagic.Storage\n\troot       *x509.Certificate\n\tinterChain []*x509.Certificate\n\tinterKey   crypto.Signer\n\tmu         *sync.RWMutex\n\n\trootCertPath string // mainly used for logging purposes if trusting\n\tlog          *zap.Logger\n\tctx          caddy.Context\n}\n\n// Provision sets up the CA.\nfunc (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {\n\tca.mu = new(sync.RWMutex)\n\tca.log = log.Named(\"ca.\" + id)\n\tca.ctx = ctx\n\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"CA ID is required (use 'local' for the default CA)\")\n\t}\n\tca.mu.Lock()\n\tca.ID = id\n\tca.mu.Unlock()\n\n\tif ca.StorageRaw != nil {\n\t\tval, err := ctx.LoadModule(ca, \"StorageRaw\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"loading storage module: %v\", err)\n\t\t}\n\t\tcmStorage, err := val.(caddy.StorageConverter).CertMagicStorage()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"creating storage configuration: %v\", err)\n\t\t}\n\t\tca.storage = cmStorage\n\t}\n\tif ca.storage == nil {\n\t\tca.storage = ctx.Storage()","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddypki/ca.go#L87-L123","documentation":"Returned at the very start of CA.Provision when the id argument is empty — every CA must have an identifier, and the default is `local`. The id is used for the logger name (ca.&lt;id&gt;), storage namespacing, and admin API paths, so an empty one is rejected outright. Users normally hit this via a config that declares a CA with an empty/missing id, or by calling Provision programmatically without an id.","triggerScenarios":"JSON config with \"pki\": {\"certificate_authorities\": {\"\": {}}} (empty key), a Go program embedding Caddy that calls ca.Provision(ctx, \"\", logger), or config tooling that strips the id when generating the pki section. Note this is the CA-level id, not the common name fields.","commonSituations":"YAML/JSON templating that renders an empty CA key when a variable is unset; automated config generation inserting a placeholder CA; refactors that moved the id into the wrong field (e.g. putting it in name instead of the map key).","solutions":["Give the CA a non-empty id — or simply remove the empty entry to use the default `local` CA","In JSON, the id is the map key under pki.certificate_authorities; make sure it is a real value","If embedding Caddy, pass a concrete id: ca.Provision(ctx, \"local\", logger)","Validate config with `caddy validate` before loading"],"exampleFix":"// before (JSON)\n\"pki\": { \"certificate_authorities\": { \"\": {} } }\n\n// after\n\"pki\": { \"certificate_authorities\": { \"local\": {} } }","handlingStrategy":"validation","validationCode":"// Validate CA ids in generated config before loading:\nfunc validateCAIDs(cas map[string]json.RawMessage) error {\n    for id := range cas {\n        if strings.TrimSpace(id) == \"\" {\n            return errors.New(\"pki.certificate_authorities has an empty CA id\")\n        }\n    }\n    return nil\n}","typeGuard":"func validCAID(id string) bool { return strings.TrimSpace(id) != \"\" }","tryCatchPattern":null,"preventionTips":["Use `local` unless multiple CAs are needed","Template guards: skip CA entries whose id variable is unset","Run `caddy validate` on rendered configs in CI"],"tags":["pki","ca","validation","config"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}