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
- Run caddy list-modules --packages to find which import paths register the colliding ID, then remove or replace one
- In go.mod, use a replace directive to point both import paths at a single copy of the plugin
- 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
- Check binary size/plugin list for accidental duplicate linking
- Use go.mod replace to dedupe fork and original
- Give forked modules distinct IDs
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
- malformed tag on field %s: %v
- missing 'namespace' key in struct tag on field %s
- unable to determine module name without inline_key when type
- unable to determine module name without inline_key because t
- expected ModuleMap because inline_key is empty; but we do no
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/f5636fbfbf8c6992.
Report an issue: GitHub.