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

  1. Retry the request on a stable connection and verify the full body is sent (Content-Length matches file size)
  2. If a reverse proxy fronts the admin endpoint, raise its request body limit (e.g. nginx client_max_body_size)
  3. Load the config from the local filesystem instead (caddy run --config) to bypass network transfer
  4. 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

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


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