caddyserver/caddy · error
config is not valid JSON: %w; did you mean to use a config a
Error message
config is not valid JSON: %w; did you mean to use a config adapter (the --adapter flag)?
What it means
Same JSON validation gate as the offset variant, but the unmarshal error was not a *json.SyntaxError — typically *json.UnmarshalTypeError (right syntax, wrong type, e.g. a string where an object is required) or unexpected EOF on a truncated file. The --adapter hint is still appended because non-JSON input is a plausible cause.
Source
Thrown at cmd/main.go:237
for _, warn := range warnings {
msg := warn.Message
if warn.Directive != "" {
msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
}
logger.Warn(msg,
zap.String("adapter", adapterName),
zap.String("file", warn.File),
zap.Int("line", warn.Line))
}
config = adaptedConfig
} else if len(config) != 0 {
// validate that the config is at least valid JSON
err = json.Unmarshal(config, new(any))
if err != nil {
if jsonErr, ok := err.(*json.SyntaxError); ok {
return nil, "", "", fmt.Errorf("config is not valid JSON: %w, at offset %d; did you mean to use a config adapter (the --adapter flag)?", err, jsonErr.Offset)
}
return nil, "", "", fmt.Errorf("config is not valid JSON: %w; did you mean to use a config adapter (the --adapter flag)?", err)
}
}
return config, configFile, adapterName, nil
}
// watchConfigFile watches the config file at filename for changes
// and reloads the config if the file was updated. This function
// blocks indefinitely; it only quits if the poller has errors for
// long enough time. The filename passed in must be the actual
// config file used, not one to be discovered.
// Each second the config files is loaded and parsed into an object
// and is compared to the last config object that was loaded
func watchConfigFile(filename, adapterName string) {
defer func() {
if err := recover(); err != nil {
log.Printf("[PANIC] watching config file: %v\n%s", err, debug.Stack())
}View on GitHub (pinned to 50e54ee279)
Solutions
- Check the type at the reported field — the error names the Go type expected
- Validate structure with jq and compare against the JSON config schema in the docs
- If the input is not JSON at all, pass --adapter
- Regenerate the file if it may be truncated (compare size against the producer's expectation)
Example fix
// before
{ "admin": "localhost:2019" }
// after
{ "admin": { "listen": "localhost:2019" } } Defensive patterns
Strategy: validation
Validate before calling
// Type-check the top-level shape before passing the config along:
var probe map[string]json.RawMessage
if err := json.Unmarshal(raw, &probe); err != nil { return fmt.Errorf("config JSON invalid: %w", err) }
if a, ok := probe["admin"]; ok {
var admin caddy.AdminConfig
if err := json.Unmarshal(a, &admin); err != nil { return fmt.Errorf("admin field has wrong type: %w", err) }
} Prevention
- Schema-check generated configs (jq or a JSON Schema) in CI
- Compare field types against the JSON config reference in the docs
When it happens
Trigger: A JSON config where a field has the wrong shape ("admin": "localhost:2019" instead of an object); a truncated file so the parser hits EOF unexpectedly; Unicode issues producing a non-SyntaxError failure.
Common situations: Hand-writing JSON configs and giving a scalar where Caddy expects an object/array; disk-full writes truncating the config.
Related errors
- unmarshaling admin listener address from config: %v
- config is not valid JSON: %w, at offset %d; did you mean to
- invalid admin address %s: %v
- caddy responded with error: HTTP %d: %s
- no config file to load; either use --config flag or ensure C
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/e48e6a571e7842c2.
Report an issue: GitHub.