caddyserver/caddy · error
%w, at offset %d
Error message
%w, at offset %d
What it means
caddy.StrictUnmarshalJSON decodes module configuration with DisallowUnknownFields; when the JSON itself is syntactically malformed, the *json.SyntaxError is re-wrapped with its byte offset so you know exactly where parsing stopped. Unknown-field errors and type errors pass through unwrapped.
Source
Thrown at modules.go:347
before, after, isCut := strings.Cut(pair, "=")
if !isCut {
return nil, fmt.Errorf("missing key in '%s' (pair %d)", pair, i)
}
results[before] = after
}
return results, nil
}
// StrictUnmarshalJSON is like json.Unmarshal but returns an error
// if any of the fields are unrecognized. Useful when decoding
// module configurations, where you want to be more sure they're
// correct.
func StrictUnmarshalJSON(data []byte, v any) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
err := dec.Decode(v)
if jsonErr, ok := err.(*json.SyntaxError); ok {
return fmt.Errorf("%w, at offset %d", jsonErr, jsonErr.Offset)
}
return err
}
var JSONRawMessageType = reflect.TypeFor[json.RawMessage]()
// isJSONRawMessage returns true if the type is encoding/json.RawMessage.
func isJSONRawMessage(typ reflect.Type) bool {
return typ == JSONRawMessageType
}
// isModuleMapType returns true if the type is map[string]json.RawMessage.
// It assumes that the string key is the module name, but this is not
// always the case. To know for sure, this function must return true, but
// also the struct tag where this type appears must NOT define an inline_key
// attribute, which would mean that the module names appear inline with the
// values, not in the key.
func isModuleMapType(typ reflect.Type) bool {View on GitHub (pinned to 50e54ee279)
Solutions
- Open the file at the exact byte offset given in the error and fix the syntax there (remember offsets are 0-based bytes, and multi-byte UTF-8 characters shift column counts).
- Run the file through a JSON linter or 'jq empty file.json' to locate all syntax errors.
- Prefer the Caddyfile + 'caddy adapt' so syntax is machine-generated.
- Check templating output (envsubst consuming braces is a classic) before loading.
Example fix
// before (json)
{"apps": {"http": {"servers": {"srv0": {"listen": [":443"],,}}}}
// after
{"apps": {"http": {"servers": {"srv0": {"listen": [":443"]}}}} Defensive patterns
Strategy: validation
Validate before calling
// pre-validate JSON syntax with offsets before loading
func jsonOK(data []byte) error {
var v any
if err := json.Unmarshal(data, &v); err != nil {
if se, ok := err.(*json.SyntaxError); ok {
return fmt.Errorf("syntax error near offset %d: %w (context: %q)", se.Offset, err, data[max(0, se.Offset-20):min(len(data), se.Offset+20)])
}
return err
}
return nil
} Try / catch
if err := caddy.Validate(cfg); err != nil {
var se *json.SyntaxError
if errors.As(err, &se) {
// jump to se.Offset in the file to fix the syntax
}
return err
} Prevention
- Lint JSON in CI with jq or a JSON schema checker.
- Quote heredoc delimiters ('EOF') to stop shell expansion of braces.
- Avoid envsubst on raw JSON — substitute a template with unique tokens instead.
When it happens
Trigger: Loading a config whose module JSON has a syntax error — trailing commas, unquoted keys, missing braces/brackets, stray characters — at the reported byte offset.
Common situations: Hand-edited JSON configs; heredocs or templating (envsubst, Helm) that mangle quotes/braces; concat of JSON fragments that leaves a dangling comma; files with a BOM or non-UTF-8 bytes.
Related errors
- unmarshaling admin listener address from config: %v
- config is not valid JSON: %w, at offset %d; did you mean to
- config is not valid JSON: %w; did you mean to use a config a
- parsing environment file: %v
- can't parse line %d; line should be in KEY=VALUE format
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/91309c858503a16e.
Report an issue: GitHub.