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

  1. 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).
  2. Run the file through a JSON linter or 'jq empty file.json' to locate all syntax errors.
  3. Prefer the Caddyfile + 'caddy adapt' so syntax is machine-generated.
  4. 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

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


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/91309c858503a16e. Report an issue: GitHub.