caddyserver/caddy · critical

module ID '%s' is reserved

Error message

module ID '%s' is reserved

What it means

The module IDs "caddy" and "admin" are reserved by the core and cannot be claimed by plugins. RegisterModule panics if a module uses one of them, because these top-level identities back core config schemas and colliding would break the config system.

Source

Thrown at modules.go:145

// json.RawMessage.
type ModuleMap map[string]json.RawMessage

// RegisterModule registers a module by receiving a
// plain/empty value of the module. For registration to
// be properly recorded, this should be called in the
// init phase of runtime. Typically, the module package
// will do this as a side-effect of being imported.
// This function panics if the module's info is
// incomplete or invalid, or if the module is already
// registered.
func RegisterModule(instance Module) {
	mod := instance.CaddyModule()

	if mod.ID == "" {
		panic("module ID missing")
	}
	if mod.ID == "caddy" || mod.ID == "admin" {
		panic(fmt.Sprintf("module ID '%s' is reserved", mod.ID))
	}
	if mod.New == nil {
		panic("missing ModuleInfo.New")
	}
	if val := mod.New(); val == nil {
		panic("ModuleInfo.New must return a non-nil module instance")
	}

	modulesMu.Lock()
	defer modulesMu.Unlock()

	if _, ok := modules[string(mod.ID)]; ok {
		panic(fmt.Sprintf("module already registered: %s", mod.ID))
	}
	modules[string(mod.ID)] = mod
}

// GetModule returns module information from its ID (full name).

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Pick a namespaced ID such as "myapp.admin" or "http.apps.myadmin"
  2. Prefix custom app IDs with your organization/plugin name to avoid all core collisions
  3. Check existing module IDs with caddy list-modules before naming

Example fix

// before
return caddy.ModuleInfo{ID: "admin", New: ...}
// after
return caddy.ModuleInfo{ID: "myplugin.admin", New: ...}
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Setting ID: "caddy" or ID: "admin" in a module's CaddyModule() and importing the package so init() runs RegisterModule.

Common situations: Writing a top-level app-like module and guessing a short ID; porting an app whose name happens to be 'admin'; misunderstanding that only core may own these roots.

Related errors


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