{"record":{"id":"dfb13d6d707397c9","repo":"caddyserver/caddy","slug":"recursive-config-loading-detected-pulled-configs","errorCode":null,"errorMessage":"recursive config loading detected: pulled configs cannot pull other configs without positive load_delay","messagePattern":"recursive config loading detected: pulled configs cannot pull other configs without positive load_delay","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"caddy.go","lineNumber":359,"sourceCode":"\n\tvar newCfg *Config\n\terr := StrictUnmarshalJSON(strippedCfgJSON, &newCfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// prevent recursive config loads; that is a user error, and\n\t// although frequent config loads should be safe, we cannot\n\t// guarantee that in the presence of third party plugins, nor\n\t// do we want this error to go unnoticed (we assume it was a\n\t// pulled config if we're not allowed to persist it)\n\tif !allowPersist &&\n\t\tnewCfg != nil &&\n\t\tnewCfg.Admin != nil &&\n\t\tnewCfg.Admin.Config != nil &&\n\t\tnewCfg.Admin.Config.LoadRaw != nil &&\n\t\tnewCfg.Admin.Config.LoadDelay <= 0 {\n\t\treturn fmt.Errorf(\"recursive config loading detected: pulled configs cannot pull other configs without positive load_delay\")\n\t}\n\n\t// run the new config and start all its apps\n\tctx, err := run(newCfg, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// swap old context (including its config) with the new one\n\tcurrentCtxMu.Lock()\n\toldCtx := currentCtx\n\tcurrentCtx = ctx\n\tcurrentCtxMu.Unlock()\n\n\t// Stop, Cleanup each old app\n\tunsyncedStop(oldCtx)\n\n\t// autosave a non-nil config, if not disabled","sourceCodeStart":341,"sourceCodeEnd":377,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/caddy.go#L341-L377","documentation":"Caddy supports pulling its config from a remote source (admin.config.load), but a config that was itself pulled must not immediately pull another config — that risks infinite load loops. unsyncedDecodeAndRun rejects such configs when allowPersist is false (i.e. the current config was pulled/non-persisted) and the new config sets admin.config.load without a strictly positive load_delay, giving Caddy a debounce window between pulls.","triggerScenarios":"Caddy started with --config pointing at a remote URL (or admin API load of a config with admin.config.load) where the pulled document itself declares admin.config.load with load_delay 0 or unset; chained remote configs A pulls B pulls A.","commonSituations":"Centralized config distribution where a pulled config still carries the load stanza from the origin file; converting a startup config to pull-mode without stripping the old admin.config.load; test setups pointing load URLs at each other.","solutions":["Set \"load_delay\": 60 (seconds, must be > 0) in the pulled config's admin.config so periodic pulls are explicit and debounced.","Or remove the admin.config.load block from the pulled document — only the bootstrap config should pull.","Ensure the pulled config's admin.config doesn't reference itself."],"exampleFix":"// before (pulled config)\n\"admin\": { \"config\": { \"load\": \"http://cfg.example/caddy.json\" } }\n\n// after\n\"admin\": { \"config\": { \"load\": \"http://cfg.example/caddy.json\", \"load_delay\": 60 } }","handlingStrategy":"validation","validationCode":"// Before serving a pulled config, ensure it cannot re-pull without a delay.\nif cfg.Admin != nil && cfg.Admin.Config != nil && cfg.Admin.Config.LoadRaw != nil && cfg.Admin.Config.LoadDelay <= 0 {\n    return errors.New(\"pulled config must set positive load_delay or drop admin.config.load\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Strip admin.config.load from documents served by a config server, or template in load_delay > 0.","Only the bootstrap config should carry the load directive.","Document the pull-chain topology so no cycle is possible."],"tags":["caddy","admin-api","config","remote-config","loop-prevention"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}