caddyserver/caddy · error

loading %s app module: %v

Error message

loading %s app module: %v

What it means

Context.App(name) loads and provisions an app module on demand. If that app previously failed to load during this config load, the failure is cached in cfg.failedApps and replayed as 'loading %s app module: %v' for every subsequent caller — so modules depending on a broken app get a consistent error instead of retrying. The %v is the original LoadModuleByID failure.

Source

Thrown at context.go:508

}

// App returns the configured app named name. If that app has
// not yet been loaded and provisioned, it will be immediately
// loaded and provisioned. If no app with that name is
// configured, a new empty one will be instantiated instead.
// (The app module must still be registered.) This must not be
// called during the Provision/Validate phase to reference a
// module's own host app (since the parent app module is still
// in the process of being provisioned, it is not yet ready).
//
// We return any type instead of the App type because it is NOT
// intended for the caller of this method to be the one to start
// or stop App modules. The caller is expected to assert to the
// concrete type.
func (ctx Context) App(name string) (any, error) {
	// if the app failed to load before, return the cached error
	if err, ok := ctx.cfg.failedApps[name]; ok {
		return nil, fmt.Errorf("loading %s app module: %v", name, err)
	}
	if app, ok := ctx.cfg.apps[name]; ok {
		return app, nil
	}
	appRaw := ctx.cfg.AppsRaw[name]
	modVal, err := ctx.LoadModuleByID(name, appRaw)
	if err != nil {
		return nil, fmt.Errorf("loading %s app module: %v", name, err)
	}
	if appRaw != nil {
		ctx.cfg.AppsRaw[name] = nil // allow GC to deallocate
	}
	return modVal, nil
}

// AppIfConfigured is like App, but it returns an error if the
// app has not been configured. This is useful when the app is
// required and its absence is a configuration error; or when

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Scroll up in the logs: the first error for this app (not this wrapped replay) is the root cause — fix that (usually a provision/validate/decode failure inside the app's own config)
  2. Validate the whole config with `caddy validate` to get a single clean error chain
  3. Correct the app-level configuration (apps section of the JSON) per the underlying error
  4. In plugin code, avoid calling ctx.App for your own host app during Provision (documented restriction) — use ctx.App for other apps only

Example fix

// before (plugin Provision)
func (h *H) Provision(ctx caddy.Context) error {
    app, err := ctx.App("http") // own host app — wrong during provisioning
// after
// design the module to receive needed data via config fields; only call ctx.App for OTHER apps
Defensive patterns

Strategy: try-catch

Try / catch

if _, err := ctx.App("tls"); err != nil {
    // cached replay: the first failure for this app is elsewhere in logs — fix that, do not retry here
    return fmt.Errorf("tls app unavailable: %w", err)
}

Prevention

When it happens

Trigger: Something calls ctx.App("tls") (or another app) after that app's provisioning already failed; the cached error branch returns fmt.Errorf("loading %s app module: %v", name, err) immediately.

Common situations: The http app failing mid-provision and a submodule then requesting the tls/pki app; cascading errors during `caddy run`/reload where the first root-cause error for the app appears earlier in logs; inter-app dependencies (tls referencing apps) surfacing repeated wrapped errors.

Related errors


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