caddyserver/caddy · info · caddy.APIError
missing CA in path
Error message
missing CA in path
What it means
A 400 caddy.APIError from getCAFromAPIRequestPath when the CA id segment of the path is empty. The function splits r.URL.Path on '/' and takes index 3 (for /pki/ca/<id>: ['', 'pki', 'ca', '<id>']); an empty segment means the request path was /pki/ca/ (trailing slash) or shorter-but-routed-here. The router's own checks normally reject such paths as 404, so seeing this 400 usually means the request arrived via an unusual path shape.
Source
Thrown at modules/caddypki/adminapi.go:180
}
}
w.Header().Set("Content-Type", "application/pem-certificate-chain")
_, err = w.Write(interCert) //nolint:gosec // false positive... no XSS in a PEM for cryin' out loud
if err == nil {
_, _ = w.Write(rootCert) //nolint:gosec // false positive... no XSS in a PEM for cryin' out loud
}
return nil
}
func (a *adminAPI) getCAFromAPIRequestPath(r *http.Request) (*CA, error) {
// Grab the CA ID from the request path, it should be the 4th segment (/pki/ca/<ca>)
id := strings.Split(r.URL.Path, "/")[3]
if id == "" {
return nil, caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("missing CA in path"),
}
}
// Find the CA by ID, if PKI is configured
var ca *CA
var ok bool
if a.pkiApp != nil {
ca, ok = a.pkiApp.CAs[id]
}
// If we didn't find the CA, and PKI is not configured
// then we'll either error out if the CA ID is not the
// default. If the CA ID is the default, then we'll
// provision it, because the user probably aims to
// change their config to enable PKI immediately after
// if they actually requested the local CA ID.
if !ok {
if id != DefaultCAID {View on GitHub (pinned to 50e54ee279)
Solutions
- Always include a concrete CA id: /pki/ca/local for the default CA
- Check shell variable expansion: curl "http://localhost:2019/pki/ca/${CA_ID}" with CA_ID unset yields exactly this class of path
- Guard scripts by defaulting the id: ${CA_ID:-local}
Example fix
# before
CA_ID=
curl "http://localhost:2019/pki/ca/$CA_ID"
# after
CA_ID=${CA_ID:-local}
curl "http://localhost:2019/pki/ca/$CA_ID" Defensive patterns
Strategy: validation
Validate before calling
// Never build a PKI path with an empty id:
func pkiURL(base, id string) (string, error) {
id = strings.TrimSpace(id)
if id == "" { return "", errors.New("CA id required") }
return base + "/pki/ca/" + id, nil
} Try / catch
// Treat 400 with 'missing CA in path' as a client bug; fail fast:
if resp.StatusCode == 400 && strings.Contains(string(body), "missing CA") {
return errors.New("URL template produced an empty CA id")
} Prevention
- Default script variables: ${CA_ID:-local}
- Lint generated URLs for empty segments/double slashes before requests
When it happens
Trigger: GET /pki/ca/ (trailing slash with empty id — normally a 404 earlier, but any path whose 4th slash-separated segment is empty when handled), or scripts that build the URL by concatenation and produce a double slash like /pki//ca// or /pki/ca//certificates.
Common situations: Template-built URLs where the id variable is empty (unset environment variable in a provisioning script); trailing-slash normalization differences between proxies in front of the admin endpoint; manual curl typos.
Related errors
- reading request body: %v
- loading config: %v
- invalid Content-Type: %v
- unrecognized config adapter '%s'
- adapting config using %s adapter: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/68b235c0559b3d92.
Report an issue: GitHub.