caddyserver/caddy · error
recursive config loading detected: pulled configs cannot pul
Error message
recursive config loading detected: pulled configs cannot pull other configs without positive load_delay
What it means
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.
Source
Thrown at caddy.go:359
var newCfg *Config
err := StrictUnmarshalJSON(strippedCfgJSON, &newCfg)
if err != nil {
return err
}
// prevent recursive config loads; that is a user error, and
// although frequent config loads should be safe, we cannot
// guarantee that in the presence of third party plugins, nor
// do we want this error to go unnoticed (we assume it was a
// pulled config if we're not allowed to persist it)
if !allowPersist &&
newCfg != nil &&
newCfg.Admin != nil &&
newCfg.Admin.Config != nil &&
newCfg.Admin.Config.LoadRaw != nil &&
newCfg.Admin.Config.LoadDelay <= 0 {
return fmt.Errorf("recursive config loading detected: pulled configs cannot pull other configs without positive load_delay")
}
// run the new config and start all its apps
ctx, err := run(newCfg, true)
if err != nil {
return err
}
// swap old context (including its config) with the new one
currentCtxMu.Lock()
oldCtx := currentCtx
currentCtx = ctx
currentCtxMu.Unlock()
// Stop, Cleanup each old app
unsyncedStop(oldCtx)
// autosave a non-nil config, if not disabledView on GitHub (pinned to 50e54ee279)
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.
Example fix
// before (pulled config)
"admin": { "config": { "load": "http://cfg.example/caddy.json" } }
// after
"admin": { "config": { "load": "http://cfg.example/caddy.json", "load_delay": 60 } } Defensive patterns
Strategy: validation
Validate before calling
// Before serving a pulled config, ensure it cannot re-pull without a delay.
if cfg.Admin != nil && cfg.Admin.Config != nil && cfg.Admin.Config.LoadRaw != nil && cfg.Admin.Config.LoadDelay <= 0 {
return errors.New("pulled config must set positive load_delay or drop admin.config.load")
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- unknown object ID '%s'
- decoding request body: %w, at offset %d
- decoding request body: %w
- no traversable path
- path missing
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/dfb13d6d707397c9.
Report an issue: GitHub.