caddyserver/caddy · error · caddy.APIError
reading request body: %v
Error message
reading request body: %v
What it means
Returned with HTTP 400 when io.Copy fails while reading the request body of a POST to the admin API /load endpoint. The body is streamed into a pooled buffer; the copy fails when the connection is broken mid-transfer, the chunked encoding is malformed, or the client aborts. It is a transport-level failure, not a config-content error.
Source
Thrown at caddyconfig/load.go:89
// config that is identical to the currently-running config
// will be a no-op unless Cache-Control: must-revalidate is set.
func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) error {
if r.Method != http.MethodPost {
return caddy.APIError{
HTTPStatus: http.StatusMethodNotAllowed,
Err: fmt.Errorf("method not allowed"),
}
}
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
_, err := io.Copy(buf, r.Body)
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("reading request body: %v", err),
}
}
body := buf.Bytes()
// if the config is formatted other than Caddy's native
// JSON, we need to adapt it before loading it
if ctHeader := r.Header.Get("Content-Type"); ctHeader != "" {
result, warnings, err := adaptByContentType(ctHeader, body)
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: err,
}
}
if len(warnings) > 0 {
respBody, err := json.Marshal(warnings)
if err != nil {
caddy.Log().Named("admin.api.load").Error(err.Error())View on GitHub (pinned to 50e54ee279)
Solutions
- Retry the request on a stable connection and verify the full body is sent (Content-Length matches file size)
- If a reverse proxy fronts the admin endpoint, raise its request body limit (e.g. nginx client_max_body_size)
- Load the config from the local filesystem instead (caddy run --config) to bypass network transfer
- Check for network instability or MTU/firewall issues between client and admin endpoint
Example fix
// before curl -m 1 -X POST http://localhost:2019/load --data-binary @huge-config.json // after curl -X POST http://localhost:2019/load -H 'Content-Type: application/json' --data-binary @huge-config.json
Defensive patterns
Strategy: retry
Validate before calling
// pre-flight: body is readable and size is known
fi, err := os.Stat(cfgPath)
if err != nil { return err }
body, err := os.ReadFile(cfgPath)
if err != nil { return err }
_ = fi Try / catch
resp, err := doLoad(ctx, body) // POST /load with ctx deadline
if resp != nil && resp.StatusCode == http.StatusBadRequest {
b, _ := io.ReadAll(resp.Body)
if strings.Contains(string(b), "reading request body") {
// transient transport failure: safe to retry with backoff
resp, err = retryWithBackoff(ctx, body, 3)
}
} Prevention
- Set an explicit generous http.Client timeout matched to config size
- Raise reverse-proxy body limits when fronting the admin endpoint
- Send bodies with --data-binary / io.ReadFull to avoid truncated uploads
When it happens
Trigger: Client disconnects (Ctrl+C, timeout) while a large config is being uploaded; a proxy or load balancer cuts the connection; malformed Content-Length or chunked transfer encoding from a hand-rolled HTTP client; request body larger than an intermediary's body-size cap.
Common situations: Uploading a multi-megabyte JSON config through nginx (default 1MB client_max_body_size) where nginx truncates and the client aborts; scripted loads killed by shell timeouts; HTTP/2 downgrade issues in fronting proxies.
Related errors
- loading config: %v
- invalid Content-Type: %v
- unrecognized config adapter '%s'
- adapting config using %s adapter: %v
- performing request: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/e10bc09053eb7296.
Report an issue: GitHub.