caddyserver/caddy · error · caddy.APIError

loading config: %v

Error message

loading config: %v

What it means

Returned with HTTP 400 by POST /load when caddy.Load() rejects the (possibly adapted) config body. caddy.Load validates, provisions, and swaps the whole config; failure means the JSON is structurally invalid, a module ID is unknown, or a module's Provision/Validate returned an error. The running configuration is untouched — the load is atomic.

Source

Thrown at caddyconfig/load.go:120

			}
		}
		if len(warnings) > 0 {
			respBody, err := json.Marshal(warnings)
			if err != nil {
				caddy.Log().Named("admin.api.load").Error(err.Error())
			}
			_, _ = w.Write(respBody) //nolint:gosec // false positive: no XSS here
		}
		body = result
	}

	forceReload := r.Header.Get("Cache-Control") == "must-revalidate"

	err = caddy.Load(body, forceReload)
	if err != nil {
		return caddy.APIError{
			HTTPStatus: http.StatusBadRequest,
			Err:        fmt.Errorf("loading config: %v", err),
		}
	}

	// If this request changed the config, clear the last
	// config info we have stored, if it is different from
	// the original source.
	caddy.ClearLastConfigIfDifferent(
		r.Header.Get("Caddy-Config-Source-File"),
		r.Header.Get("Caddy-Config-Source-Adapter"))

	caddy.Log().Named("admin.api").Info("load complete")

	return nil
}

// handleAdapt adapts the given Caddy config to JSON and responds with the result.
func (adminLoad) handleAdapt(w http.ResponseWriter, r *http.Request) error {
	if r.Method != http.MethodPost {

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Run caddy validate --config <file> [--adapter <name>] against the same config first — it reports the precise validation error
  2. Validate the JSON syntax locally (jq . config.json) before posting
  3. Read the error detail in the response body and in the server logs — it names the failing module and field
  4. If a module is reported unknown, rebuild Caddy with the required plugin (xcaddy build --with ...)

Example fix

// before
curl -X POST http://localhost:2019/load -d @broken.json
// after
caddy validate --config ./broken.json && curl -X POST http://localhost:2019/load -H 'Content-Type: application/json' -d @broken.json
Defensive patterns

Strategy: validation

Validate before calling

// dry-run the exact bytes through the same pipeline first
func safeLoad(ctx context.Context, client *http.Client, cfg []byte) error {
    req, _ := http.NewRequestWithContext(ctx, "POST", "http://localhost:2019/load", bytes.NewReader(cfg))
    req.Header.Set("Content-Type", "application/json")
    resp, err := client.Do(req)
    if err != nil { return err }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK {
        b, _ := io.ReadAll(resp.Body)
        return fmt.Errorf("load rejected (%d): %s — validate config first", resp.StatusCode, b)
    }
    return nil
}

Try / catch

if resp.StatusCode == http.StatusBadRequest {
    var apiErr struct{ Error string }
    json.NewDecoder(resp.Body).Decode(&apiErr)
    if strings.Contains(apiErr.Error, "loading config") {
        // config rejected: do NOT retry; fix config and re-validate
        log.Printf("config rejected: %s", apiErr.Error)
    }
}

Prevention

When it happens

Trigger: Posting JSON with syntax errors or wrong schema (e.g. missing apps wrapper); referencing a module that is not compiled into the binary (e.g. dns challenge providers in a custom build); a module failing Validate (e.g. tls automation with no email and no local issuer config); adapter output that is valid JSON but semantically wrong.

Common situations: CI/CD pipelines pushing generated config where a field was renamed between Caddy versions; hand-edited JSON with trailing commas; adapting a Caddyfile that uses a plugin not included in the build; loading config referencing http.handlers.foo in a build without that plugin.

Related errors


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