{"record":{"id":"ec142ec5d08913cf","repo":"caddyserver/caddy","slug":"loading-handler-modules-v","errorCode":null,"errorMessage":"loading handler modules: %v","messagePattern":"loading handler modules: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/routes.go","lineNumber":163,"sourceCode":"\tmatchersIface, err := ctx.LoadModule(r, \"MatcherSetsRaw\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"loading matcher modules: %v\", err)\n\t}\n\terr = r.MatcherSets.FromInterface(matchersIface)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// ProvisionHandlers sets up all the handlers by loading the\n// handler modules. Only call this method directly if you need\n// to set up matchers and handlers separately without having\n// to provision a second time; otherwise use Provision instead.\nfunc (r *Route) ProvisionHandlers(ctx caddy.Context, metrics *Metrics) error {\n\thandlersIface, err := ctx.LoadModule(r, \"HandlersRaw\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"loading handler modules: %v\", err)\n\t}\n\tfor _, handler := range handlersIface.([]any) {\n\t\tr.Handlers = append(r.Handlers, handler.(MiddlewareHandler))\n\t}\n\n\t// Store metrics info for route-level instrumentation (applied once\n\t// per route in wrapRoute, instead of per-handler which was redundant).\n\tr.metrics = metrics\n\tr.metricsCtx = ctx\n\tif len(r.Handlers) > 0 {\n\t\tr.handlerName = caddy.GetModuleName(r.Handlers[0])\n\t}\n\n\t// Make ProvisionHandlers idempotent by clearing the middleware field\n\tr.middleware = []Middleware{}\n\n\t// pre-compile the middleware handler chain\n\tfor _, midhandler := range r.Handlers {","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/routes.go#L145-L181","documentation":"Route.ProvisionHandlers loads the handler chain for a route via ctx.LoadModule; this error wraps any failure to instantiate/provision a handler. Causes include unknown handler module IDs, wrong JSON shape for a handler, or a handler plugin absent from the binary.","triggerScenarios":"A route whose \"handle\" array references \"http.handlers.fil_server\" (typo), passes a string where a number is expected, or uses a third-party handler not compiled in.","commonSituations":"Hand-written JSON configs, moving a config from an xcaddy build to a standard caddy binary, or upgrading Caddy where a handler's config schema changed.","solutions":["Inspect the wrapped error for the exact module and reason","Run 'caddy list-modules --packages http.handlers' to confirm the handler exists in this build","Correct the handler name/fields in the route","Rebuild with xcaddy if the handler comes from a plugin"],"exampleFix":"// before\n\"handle\": [{ \"handler\": \"reverse_proxy\", \"upstream\": \"localhost:8080\" }]\n\n// after\n\"handle\": [{ \"handler\": \"reverse_proxy\", \"upstreams\": [{ \"dial\": \"localhost:8080\" }] }]","handlingStrategy":"validation","validationCode":"// CLI check before deploy:\n// caddy validate --config Caddyfile\n// caddy list-modules --packages http.handlers\n\n// library: ensure each handler name is registered\nfor _, h := range route.HandlersRaw {\n    name, _ := h.(map[string]any) // first key is module name\n    for k := range name {\n        if !knownHandlers[k] { return fmt.Errorf(\"unknown handler %q\", k) }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate config with the same binary that will serve it","When removing a plugin from an xcaddy build, grep configs for its handler ID","Adapt Caddyfile to JSON and schema-check before applying"],"tags":["caddy","config","handlers","module-loading","routing"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}