caddyserver/caddy · error
loading dynamic config from %T: %v
Error message
loading dynamic config from %T: %v
What it means
Thrown when the dynamic config loader module implements ConfigLoader and its LoadConfig(ctx) returns an error in the synchronous path (no LoadDelay configured). The message includes %T, the concrete loader module type, so you can identify which loader failed.
Source
Thrown at caddy.go:670
err = runLoadedConfig(loadedConfig)
if errors.Is(err, errSameConfig) {
logger.Info("dynamically-loaded config was unchanged; will retry")
continue
}
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
logger.Info("stopping dynamic config loading")
}
break
}
}()
} else {
// if no LoadDelay is provided, will load config synchronously
loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx)
if err != nil {
return fmt.Errorf("loading dynamic config from %T: %v", val, err)
}
// do this in a goroutine so current config can finish being loaded; otherwise deadlock
go func() { _ = runLoadedConfig(loadedConfig) }()
}
}
return nil
}
// ConfigLoader is a type that can load a Caddy config. If
// the return value is non-nil, it must be valid Caddy JSON;
// if nil or with non-nil error, it is considered to be a
// no-op load and may be retried later.
type ConfigLoader interface {
LoadConfig(Context) ([]byte, error)
}
// Stop stops running the current configuration.View on GitHub (pinned to 50e54ee279)
Solutions
- Check which loader type appears in the message and inspect that module's LoadConfig error semantics
- For the http loader: verify the URL, network reachability, TLS trust, and that the body is valid Caddy JSON
- Set a load_delay so loading happens asynchronously and a transient fetch failure does not abort startup
- Test the remote config payload with `caddy validate --adapter json --config <file>` or `curl` first
Example fix
// before
{"admin": {"config": {"load": {"http": {"url": "http://cfg.internal/current"}}}}}
// after (retry-friendly async load)
{"admin": {"config": {"load_delay": "5s", "load": {"http": {"url": "http://cfg.internal/current"}}}}} Defensive patterns
Strategy: fallback
Validate before calling
// for the http loader: health-check the config endpoint before Caddy starts
resp, err := http.Get(cfgURL)
if err != nil || resp.StatusCode != 200 {
log.Fatal("remote config unavailable")
}
body, _ := io.ReadAll(resp.Body)
if err := json.Valid(body); ! err { log.Fatal("remote config is not valid JSON") } Try / catch
if err := runLoad(); err != nil {
if strings.Contains(err.Error(), "loading dynamic config from") {
// fall back to last-known-good config file instead of aborting
}
} Prevention
- Set load_delay so sync failures don't abort startup
- Serve config from a highly available endpoint with retries
- Keep a last-known-good config on disk as fallback
When it happens
Trigger: admin.config.load is set, LoadDelay is 0/unset, and val.(ConfigLoader).LoadConfig(ctx) returns a non-nil error — e.g. the HTTP loader got a non-200 response, or a custom loader could not fetch/decode its payload.
Common situations: Config pulled from a remote URL at startup where the endpoint is down, returns invalid Caddy JSON, or requires auth that was not configured. Because it fires synchronously, Caddy startup aborts.
Related errors
- loading config loader module: %s
- server responded with HTTP %d
- problem calling http loader url: %v
- listening on %s: %v
- network '%s' cannot handle HTTP/1 or HTTP/2 connections
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/f0a7b6007c628c3a.
Report an issue: GitHub.