caddyserver/caddy · error · APIError
indexing config: %v
Error message
indexing config: %v
What it means
changeConfig walks the new config tree with indexConfigObjects to register every '@id' field so /id/... admin paths can address config objects. Any object whose '@id' is not a string or number, or any duplicate '@id', aborts the change with this APIError (HTTP 400). On this path Caddy first restores rawCfg from the last good encoded copy, so the running config is unaffected.
Source
Thrown at caddy.go:241
}
// find any IDs in this config and index them
idx := make(map[string]string)
err = indexConfigObjects(rawCfg[rawConfigKey], "/"+rawConfigKey, idx)
if err != nil {
if len(rawCfgJSON) > 0 {
var oldCfg any
err2 := json.Unmarshal(rawCfgJSON, &oldCfg)
if err2 != nil {
err = fmt.Errorf("%v; additionally, restoring old config: %v", err, err2)
}
rawCfg[rawConfigKey] = oldCfg
} else {
rawCfg[rawConfigKey] = nil
}
return APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("indexing config: %v", err),
}
}
// load this new config; if it fails, we need to revert to
// our old representation of caddy's actual config
err = unsyncedDecodeAndRun(newCfg, true)
if err != nil {
if len(rawCfgJSON) > 0 {
// restore old config state to keep it consistent
// with what caddy is still running; we need to
// unmarshal it again because it's likely that
// pointers deep in our rawCfg map were modified
var oldCfg any
err2 := json.Unmarshal(rawCfgJSON, &oldCfg)
if err2 != nil {
err = fmt.Errorf("%v; additionally, restoring old config: %v", err, err2)
}
rawCfg[rawConfigKey] = oldCfgView on GitHub (pinned to 50e54ee279)
Solutions
- Make every '@id' a unique string (or number) — e.g. "@id": "srv0-tls".
- Search the submitted JSON for duplicate '"@id"' keys sharing the same value and rename them.
- Remove '@id' fields you do not need for /id/ addressing.
Example fix
// before
{ "@id": true, "apps": { ... } }
// after
{ "@id": "main", "apps": { ... } } Defensive patterns
Strategy: validation
Validate before calling
// Before submitting, check every @id is string/number and unique.
func checkIDs(node any, seen map[string]string, path string) error {
m, ok := node.(map[string]any)
if !ok { return nil }
if id, ok := m["@id"]; ok {
s, ok := id.(string)
if !ok { _, ok = id.(float64) }
if !ok { return fmt.Errorf("%s: @id not string/number", path) }
if _, dup := seen[fmt.Sprint(id)]; dup { return fmt.Errorf("duplicate @id %v at %s", id, path) }
seen[fmt.Sprint(id)] = path
}
for k, v := range m { if err := checkIDs(v, seen, path+"/"+k); err != nil { return err } }
return nil
} Prevention
- Use only unique string @id values.
- Lint generated configs for duplicate or non-scalar @id before POSTing.
- Prefer caddy validate on the JSON config before loading it via the API.
When it happens
Trigger: POSTing/PUTing a JSON config where an '@id' value is an object, array, boolean, or null; assigning the same '@id' string to two objects; PATCHing /config/ subtrees that introduce colliding IDs. Does not apply to Caddyfile-adapted configs unless they emit @id.
Common situations: Hand-written JSON configs with '@id': true or nested objects; generated configs where a template reuses an id; merging config fragments that each carry '@id': 'my-app'.
Related errors
- %s: %s field must be a string or number
- duplicate ID '%s' found at %s and %s
- parsing listener address: %v
- must be exactly one listener address; cannot listen on: %s
- %v; additionally, restoring old config: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/b7d597df72464d84.
Report an issue: GitHub.