caddyserver/caddy · critical

module already registered: %s

Error message

module already registered: %s

What it means

The global module registry is keyed by string ID; registering the same ID twice panics. This is almost always caused by a plugin being linked into the binary twice (different import paths after forking/vendoring) or two modules that chose the same ID.

Source

Thrown at modules.go:158

	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).
func GetModule(name string) (ModuleInfo, error) {
	modulesMu.RLock()
	defer modulesMu.RUnlock()
	m, ok := modules[name]
	if !ok {
		return ModuleInfo{}, fmt.Errorf("module not registered: %s", name)
	}
	return m, nil
}

// GetModuleName returns a module's name (the last label of its ID)
// from an instance of its value. If the value is not a module, an
// empty string will be returned.

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Run caddy list-modules --packages to find which import paths register the colliding ID, then remove or replace one
  2. In go.mod, use a replace directive to point both import paths at a single copy of the plugin
  3. If you own the module, give forks a distinct ID

Example fix

# before (two copies linked)
go mod edit -replace github.com/original/plugin=./fork
# keep only one; in go.mod of the consuming module:
# after
require github.com/original/plugin v1.2.3
replace github.com/original/plugin => github.com/myorg/plugin v1.2.3
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Importing the same plugin package under two paths (e.g. original plus fork), or two distinct packages both registering ID "http.handlers.custom"; init() of both runs RegisterModule with the same ID.

Common situations: Using xcaddy with a fork while the original is still pulled by another dependency; renaming a module package but keeping its ID while both get linked; duplicate builds after vendoring.

Related errors


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