caddyserver/caddy · error · APIError
encoding new config
Error message
encoding new config: %v
What it means
After a successful mutation of the in-memory raw config, changeConfig re-encodes the whole config with json.Marshal. If any value in the raw config map cannot be represented as JSON (unsupported type injected by a module or plugin, NaN/Inf float, circular structure), marshaling fails and the request aborts with an APIError (HTTP 400) wrapping 'encoding new config'. The raw config is left mutated but not loaded.
Solutions
- Inspect the wrapped %v — it names the offending type/field that json.Marshal choked on.
- Remove or fix the module/plugin that injects non-serializable values into the config tree.
- If you control the code, store only JSON-compatible data (strings, numbers, bools, maps, slices) in raw config paths.
- Restart from a known-good config file (caddy run --config) to clear the mutated in-memory state.
Defensive patterns
Strategy: try-catch
Try / catch
err := changeConfig(...) // or admin POST
if err != nil {
if apiErr, ok := err.(caddy.APIError); ok && strings.Contains(apiErr.Error(), "encoding new config") {
// in-memory raw config mutated but not loaded: restart from file
log.Fatal("config serialization failed; restart from known-good file")
}
} Prevention
- Never inject non-JSON values (funcs, NaN, cycles) into raw config structures.
- Keep custom modules' stored config data strictly JSON-typed.
- Always keep the canonical config on disk so a restart can recover state.
When it happens
Trigger: A plugin or custom admin handler inserting non-JSON-serializable values (funcs, channels, NaN) into rawCfg before changeConfig re-encodes; extremely rare via stock Caddy because rawCfg originates from a JSON decode. JSON config paths (/config/, /id/) that programmatically set exotic values.
Common situations: Third-party modules hooking config storage; patched/forked Caddy builds; almost never seen with unmodified Caddy and file-based configs. When it does occur the culprit is usually a recently added custom module.
Related errors
- decoding request body
- decoding request body
- encoding config
- final element is not an array
- marshaling %T matcher
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/6f7802859fbfa980.
Report an issue: GitHub.
Appendix: source
Thrown at caddy.go:215
if hex.EncodeToString(hash.Sum(nil)) != parts[1] {
return APIError{
HTTPStatus: http.StatusPreconditionFailed,
Err: fmt.Errorf("If-Match header did not match current config hash"),
}
}
}
err := unsyncedConfigAccess(method, path, input, nil)
if err != nil {
return err
}
// the mutation is complete, so encode the entire config as JSON
newCfg, err := json.Marshal(rawCfg[rawConfigKey])
if err != nil {
return APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("encoding new config: %v", err),
}
}
// if nothing changed, no need to do a whole reload unless the client forces it
if !forceReload && bytes.Equal(rawCfgJSON, newCfg) {
Log().Info("config is unchanged")
return errSameConfig
}
// 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)View on GitHub (pinned to 50e54ee279)