caddyserver/caddy · error
config is not valid JSON: %w, at offset %d; did you mean to
Error message
config is not valid JSON: %w, at offset %d; did you mean to use a config adapter (the --adapter flag)?
What it means
No adapter applied, so Caddy validated the raw config as JSON and hit a json.SyntaxError — structural JSON breakage (trailing comma, unquoted key, stray character). The byte offset of the failure is included, and the hint suggests --adapter for non-JSON inputs. This is the offset-carrying variant of the two JSON validation messages.
Source
Thrown at cmd/main.go:235
}
logger.Info("adapted config to JSON", zap.String("adapter", adapterName))
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 {View on GitHub (pinned to 50e54ee279)
Solutions
- If the file is a Caddyfile, add --adapter caddyfile (or name the file 'Caddyfile' for auto-detection)
- If it is JSON, jump to the reported offset and fix the syntax error
- Lint first: jq . config.json — jq pinpoints the same offset
Example fix
# before caddy run --config Caddyfile.prod # it is Caddyfile syntax, parsed as JSON # after caddy run --config Caddyfile.prod --adapter caddyfile
Defensive patterns
Strategy: validation
Validate before calling
// Detect non-JSON input up front and require an adapter:
if !json.Valid(rawConfig) { if adapterName == "" { return errors.New("config is not JSON; pass --adapter (e.g. caddyfile)") } } Type guard
func looksLikeJSON(b []byte) bool { t := bytes.TrimSpace(b); return len(t) > 0 && (t[0] == '{' || t[0] == '[') } Prevention
- Name Caddyfile-syntax files 'Caddyfile*' for auto-detection
- Always pass --adapter for non-JSON config formats
- Use the reported byte offset to jump straight to the syntax error
When it happens
Trigger: Passing a Caddyfile (or YAML etc.) as --config without --adapter; hand-edited JSON config with a syntax slip; the offset in the message maps directly to the offending byte.
Common situations: Forgetting that JSON, not Caddyfile, is the native format; tools emitting near-JSON (comments, trailing commas) that strict encoding/json rejects.
Related errors
- unmarshaling admin listener address from config: %v
- adapting config using %s: %v
- config is not valid JSON: %w; did you mean to use a config a
- invalid admin address %s: %v
- caddy responded with error: HTTP %d: %s
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/b018ec0201ded4c3.
Report an issue: GitHub.