{"record":{"id":"f99e0603b76457eb","repo":"caddyserver/caddy","slug":"loading-config-v","errorCode":null,"errorMessage":"loading config: %v","messagePattern":"loading config: (.+?)","errorType":"http","errorClass":"caddy.APIError","httpStatus":400,"severity":"error","filePath":"caddyconfig/load.go","lineNumber":120,"sourceCode":"\t\t\t}\n\t\t}\n\t\tif len(warnings) > 0 {\n\t\t\trespBody, err := json.Marshal(warnings)\n\t\t\tif err != nil {\n\t\t\t\tcaddy.Log().Named(\"admin.api.load\").Error(err.Error())\n\t\t\t}\n\t\t\t_, _ = w.Write(respBody) //nolint:gosec // false positive: no XSS here\n\t\t}\n\t\tbody = result\n\t}\n\n\tforceReload := r.Header.Get(\"Cache-Control\") == \"must-revalidate\"\n\n\terr = caddy.Load(body, forceReload)\n\tif err != nil {\n\t\treturn caddy.APIError{\n\t\t\tHTTPStatus: http.StatusBadRequest,\n\t\t\tErr:        fmt.Errorf(\"loading config: %v\", err),\n\t\t}\n\t}\n\n\t// If this request changed the config, clear the last\n\t// config info we have stored, if it is different from\n\t// the original source.\n\tcaddy.ClearLastConfigIfDifferent(\n\t\tr.Header.Get(\"Caddy-Config-Source-File\"),\n\t\tr.Header.Get(\"Caddy-Config-Source-Adapter\"))\n\n\tcaddy.Log().Named(\"admin.api\").Info(\"load complete\")\n\n\treturn nil\n}\n\n// handleAdapt adapts the given Caddy config to JSON and responds with the result.\nfunc (adminLoad) handleAdapt(w http.ResponseWriter, r *http.Request) error {\n\tif r.Method != http.MethodPost {","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/caddyconfig/load.go#L102-L138","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Run caddy validate --config <file> [--adapter <name>] against the same config first — it reports the precise validation error","Validate the JSON syntax locally (jq . config.json) before posting","Read the error detail in the response body and in the server logs — it names the failing module and field","If a module is reported unknown, rebuild Caddy with the required plugin (xcaddy build --with ...)"],"exampleFix":"// before\ncurl -X POST http://localhost:2019/load -d @broken.json\n// after\ncaddy validate --config ./broken.json && curl -X POST http://localhost:2019/load -H 'Content-Type: application/json' -d @broken.json","handlingStrategy":"validation","validationCode":"// dry-run the exact bytes through the same pipeline first\nfunc safeLoad(ctx context.Context, client *http.Client, cfg []byte) error {\n    req, _ := http.NewRequestWithContext(ctx, \"POST\", \"http://localhost:2019/load\", bytes.NewReader(cfg))\n    req.Header.Set(\"Content-Type\", \"application/json\")\n    resp, err := client.Do(req)\n    if err != nil { return err }\n    defer resp.Body.Close()\n    if resp.StatusCode != http.StatusOK {\n        b, _ := io.ReadAll(resp.Body)\n        return fmt.Errorf(\"load rejected (%d): %s — validate config first\", resp.StatusCode, b)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if resp.StatusCode == http.StatusBadRequest {\n    var apiErr struct{ Error string }\n    json.NewDecoder(resp.Body).Decode(&apiErr)\n    if strings.Contains(apiErr.Error, \"loading config\") {\n        // config rejected: do NOT retry; fix config and re-validate\n        log.Printf(\"config rejected: %s\", apiErr.Error)\n    }\n}","preventionTips":["Always run caddy validate on the config file before posting to /load","Post to /adapt first as a dry-run when using a config adapter","Pin module availability: build with xcaddy and verify with caddy list-modules in CI"],"tags":["admin-api","config","validation","http-400"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}