{"record":{"id":"c1b31bae088cbeab","repo":"caddyserver/caddy","slug":"server-responded-with-http-d","errorCode":null,"errorMessage":"server responded with HTTP %d","messagePattern":"server responded with HTTP (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"caddyconfig/httploader.go","lineNumber":114,"sourceCode":"\n\turl := repl.ReplaceAll(hl.URL, \"\")\n\treq, err := http.NewRequestWithContext(ctx, method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor key, vals := range hl.Headers {\n\t\tfor _, val := range vals {\n\t\t\treq.Header.Add(repl.ReplaceAll(key, \"\"), repl.ReplaceKnown(val, \"\"))\n\t\t}\n\t}\n\n\tresp, err := doHttpCallWithRetries(ctx, client, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode >= 400 {\n\t\treturn nil, fmt.Errorf(\"server responded with HTTP %d\", resp.StatusCode)\n\t}\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// adapt the config based on either manually-configured adapter or server's response header\n\tct := resp.Header.Get(\"Content-Type\")\n\tif hl.Adapter != \"\" {\n\t\tct = \"text/\" + hl.Adapter\n\t}\n\tresult, warnings, err := adaptByContentType(ct, body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, warn := range warnings {\n\t\tctx.Logger().Warn(warn.String())","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/caddyconfig/httploader.go#L96-L132","documentation":"The HTTP config loader (loading the running config over HTTP(S)) treats a final response with status >= 400 as a hard failure, reporting the status code. Because attemptHttpCall already rejects anything outside 200-499 and retries it, the responses that actually reach this check are 4xx ones (404, 401, 403, ...) — client-side errors that retrying will not fix.","triggerScenarios":"Running Caddy with a config URL where the endpoint returns 404 (wrong path), 401/403 (auth required but no/insufficient headers configured), or any 4xx on the first attempt.","commonSituations":"Centralized config distribution where the config service requires a token not sent; typo in the config URL path; endpoint moved after a config-service upgrade.","solutions":["curl the exact URL from the Caddy host and inspect status/body; fix the path or endpoint.","If auth is required, configure the loader's 'headers' block (e.g. Authorization bearer token).","Check the config server's logs for why it rejected the request."],"exampleFix":"# before\n{\n  admin off\n  config_loaders {\n    http http://cfg.internal/caddy/config\n  }\n}\n\n# after\n{\n  admin off\n  config_loaders {\n    http http://cfg.internal/caddy/config {\n      headers Authorization \"Bearer {env.CFG_TOKEN}\"\n    }\n  }\n}","handlingStrategy":"retry","validationCode":"curl -fsS -H \"Authorization: Bearer $CFG_TOKEN\" \"$CONFIG_URL\" >/dev/null && echo endpoint-ok || echo endpoint-broken","typeGuard":null,"tryCatchPattern":"// if embedding Caddy: treat config-load status errors as non-retryable client errors\nif err := loadConfig(ctx); err != nil {\n\tif strings.Contains(err.Error(), \"server responded with HTTP\") {\n\t\t// 4xx from the config endpoint: fix URL/headers, do not retry blindly\n\t\tlog.Fatal(err)\n\t}\n\treturn err\n}","preventionTips":["Health-check the config endpoint (with the same headers) before (re)starting Caddy.","Send required auth headers via the http loader's headers option.","Keep the config endpoint returning 2xx for success."],"tags":["config-loader","http","network","status-code"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}