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 disabled

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Set "load_delay": 60 (seconds, must be > 0) in the pulled config's admin.config so periodic pulls are explicit and debounced.
  2. Or remove the admin.config.load block from the pulled document — only the bootstrap config should pull.
  3. 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

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


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/dfb13d6d707397c9. Report an issue: GitHub.