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

  1. Always include a concrete CA id: /pki/ca/local for the default CA
  2. Check shell variable expansion: curl "http://localhost:2019/pki/ca/${CA_ID}" with CA_ID unset yields exactly this class of path
  3. 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

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


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