caddyserver/caddy · error · caddy.APIError

invalid Content-Type: %v

Error message

invalid Content-Type: %v

What it means

Returned with HTTP 400 when the request's Content-Type header cannot be parsed by Go's mime.ParseMediaType on /load or /adapt. The header must be a valid MIME type with optional parameters, e.g. 'text/caddyfile; charset=utf-8'. Garbage like 'caddyfile' alone, empty type with parameters (';charset=utf-8'), or stray characters cause the parse to fail.

Source

Thrown at caddyconfig/load.go:189

	}

	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(out)
}

// adaptByContentType adapts body to Caddy JSON using the adapter specified by contentType.
// If contentType is empty or ends with "/json", the input will be returned, as a no-op.
func adaptByContentType(contentType string, body []byte) ([]byte, []Warning, error) {
	// assume JSON as the default
	if contentType == "" {
		return body, nil, nil
	}

	ct, _, err := mime.ParseMediaType(contentType)
	if err != nil {
		return nil, nil, caddy.APIError{
			HTTPStatus: http.StatusBadRequest,
			Err:        fmt.Errorf("invalid Content-Type: %v", err),
		}
	}

	// if already JSON, no need to adapt
	if strings.HasSuffix(ct, "/json") {
		return body, nil, nil
	}

	// adapter name should be suffix of MIME type
	_, adapterName, slashFound := strings.Cut(ct, "/")
	if !slashFound {
		return nil, nil, fmt.Errorf("malformed Content-Type")
	}

	cfgAdapter := GetAdapter(adapterName)
	if cfgAdapter == nil {
		return nil, nil, fmt.Errorf("unrecognized config adapter '%s'", adapterName)
	}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Use a well-formed MIME type: Content-Type: text/caddyfile (the part after the slash names the adapter)
  2. Omit Content-Type entirely if the body is already Caddy JSON (it defaults to JSON)
  3. End JSON types with /json, e.g. application/json, to skip adaptation

Example fix

# before
curl -X POST http://localhost:2019/load -H 'Content-Type: caddyfile' -d @Caddyfile
# after
curl -X POST http://localhost:2019/load -H 'Content-Type: text/caddyfile' -d @Caddyfile
Defensive patterns

Strategy: validation

Validate before calling

// validate the header before sending
func validCT(ct string) bool {
    if ct == "" { return true } // JSON default
    t, _, err := mime.ParseMediaType(ct)
    return err == nil && strings.Contains(t, "/")
}
if !validCT(ct) { return fmt.Errorf("bad Content-Type %q; use e.g. text/caddyfile", ct) }

Try / catch

if resp.StatusCode == http.StatusBadRequest && strings.Contains(bodyText, "invalid Content-Type") {
    // fix header client-side; retrying unchanged will always fail
    req.Header.Set("Content-Type", "application/json")
}

Prevention

When it happens

Trigger: Setting Content-Type: caddyfile (no slash, no type/subtype structure); Content-Type with malformed parameters like 'text/caddyfile;;'; duplicated headers producing an invalid joined value; clients sending 'Content-Type: ' with trailing junk.

Common situations: Scripts that set the adapter name directly as the Content-Type instead of text/<adapter>; copy-paste of header values with smart quotes or whitespace; HTTP libraries auto-appending broken charset parameters.

Related errors


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