caddyserver/caddy · error
decoding request body: %w, at offset %d
Error message
decoding request body: %w, at offset %d
What it means
Returned by unsyncedConfigAccess when the JSON request body cannot be unmarshaled and the failure is a *json.SyntaxError. The error includes the byte offset where JSON parsing failed, e.g. 'decoding request body: invalid character '\'' looking for beginning of value, at offset 2'. This is a malformed-JSON-body problem, not a schema problem.
Source
Thrown at admin.go:1170
// unsyncedConfigAccess traverses into the current config and performs
// the operation at path according to method, using body and out as
// needed. This is a low-level, unsynchronized function; most callers
// will want to use changeConfig or readConfig instead. This requires a
// read or write lock on currentCtxMu, depending on method (GET needs
// only a read lock; all others need a write lock).
func unsyncedConfigAccess(method, path string, body []byte, out io.Writer) error {
var err error
var val any
// if there is a request body, decode it into the
// variable that will be set in the config according
// to method and path
if len(body) > 0 {
err = json.Unmarshal(body, &val)
if err != nil {
if jsonErr, ok := err.(*json.SyntaxError); ok {
return fmt.Errorf("decoding request body: %w, at offset %d", jsonErr, jsonErr.Offset)
}
return fmt.Errorf("decoding request body: %w", err)
}
}
enc := json.NewEncoder(out)
cleanPath := strings.Trim(path, "/")
if cleanPath == "" {
return fmt.Errorf("no traversable path")
}
parts := strings.Split(cleanPath, "/")
if len(parts) == 0 {
return fmt.Errorf("path missing")
}
// A path that ends with "..." implies:View on GitHub (pinned to 50e54ee279)
Solutions
- Validate the exact bytes you send with a JSON linter or jq: echo "$BODY" | jq .
- Use a real JSON serializer in your script/client instead of string concatenation
- Use a heredoc with quoted delimiter in curl so the shell does not mangle quotes: curl -X POST --data @- http://localhost:2019/config/apps/http/servers <<'EOF' ... EOF
- Check the reported offset against your payload to find the exact broken byte
Example fix
# before
curl -X POST http://localhost:2019/config/apps/http/servers/myserver/routes \
-d "{ listen: [':8080'] ,}" # invalid JSON
# after
curl -X POST http://localhost:2019/config/apps/http/servers/myserver/routes \
-H 'Content-Type: application/json' \
-d '{"listen":[":8080"]}' Defensive patterns
Strategy: validation
Validate before calling
import json
def safe_config_call(body: str):
json.loads(body) # raises locally with a clear error before touching Caddy
return body Try / catch
Catch locally first: json.loads()/jq validation before send; on 400 from Caddy, parse the offset from 'at offset N' and point at byte N of your payload.
Prevention
- Always serialize JSON with a library, never string concatenation
- In curl heredocs use <<'EOF' (quoted) to stop shell quote mangling
When it happens
Trigger: POST/PUT/PATCH to /config/... with syntactically broken JSON: trailing commas, single quotes, unescaped newlines in strings, wrong content-type encoding, or a truncated body cut off by a proxy.
Common situations: Hand-written curl payloads with shell quoting mistakes; JSON produced by string concatenation instead of a serializer; request bodies truncated by a reverse proxy or client timeout; comments in JSON (JSON5 style) which Go's decoder rejects.
Related errors
- decoding request body: %w
- final element is not an array
- [%s] invalid array index '%s': %v
- [%s] array index out of bounds: %s
- [/%s] invalid array index '%s': %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/d249837d22925fcc.
Report an issue: GitHub.