caddyserver/caddy · critical
network type %s is already registered
Error message
network type %s is already registered
What it means
RegisterNetwork maintains a global registry of custom network types; registering the same (case-insensitive, trimmed) name twice panics. This usually means two plugins claim the same network name, or one plugin registers in multiple init paths. The panic fires during process startup, before serving.
Source
Thrown at listeners.go:677
}
// RegisterNetwork registers a network type with Caddy so that if a listener is
// created for that network type, getListener will be invoked to get the listener.
// This should be called during init() and will panic if the network type is standard
// or reserved, or if it is already registered. EXPERIMENTAL and subject to change.
func RegisterNetwork(network string, getListener ListenerFunc) {
network = strings.TrimSpace(strings.ToLower(network))
if network == "tcp" || network == "tcp4" || network == "tcp6" ||
network == "udp" || network == "udp4" || network == "udp6" ||
network == "unix" || network == "unixpacket" || network == "unixgram" ||
strings.HasPrefix(network, "ip:") || strings.HasPrefix(network, "ip4:") || strings.HasPrefix(network, "ip6:") ||
network == "fd" || network == "fdgram" {
panic("network type " + network + " is reserved")
}
if _, ok := networkTypes[strings.ToLower(network)]; ok {
panic("network type " + network + " is already registered")
}
networkTypes[network] = getListener
}
var unixSocketsMu sync.Mutex
// getListenerFromPlugin returns a listener on the given network and address
// if a plugin has registered the network name. It may return (nil, nil) if
// no plugin can provide a listener.
func getListenerFromPlugin(ctx context.Context, network, host, port string, portOffset uint, config net.ListenConfig) (any, error) {
// get listener from plugin if network type is registered
if getListener, ok := networkTypes[network]; ok {
Log().Debug("getting listener from plugin", zap.String("network", network))
return getListener(ctx, network, host, port, portOffset, config)
}
return nil, nilView on GitHub (pinned to 50e54ee279)
Solutions
- Give your network type a unique namespaced name (e.g. "acme:wire")
- Check which plugins are compiled in: caddy list-modules --packages, and remove the duplicate import
- If you forked a plugin, exclude the original from the build (replace/drop the import)
Example fix
// before
// plugin A and plugin B both:
func init() { caddy.RegisterNetwork("tunnel", fnA) }
// after
// plugin A:
func init() { caddy.RegisterNetwork("acme:tunnel", fnA) }
// plugin B:
func init() { caddy.RegisterNetwork("othercorp:tunnel", fnB) } Defensive patterns
Strategy: validation
Prevention
- Prefix network names with a plugin/vendor identifier
- Audit linked plugins with caddy list-modules --packages
- Use go.mod replace to collapse duplicate plugin copies
When it happens
Trigger: Two plugins both calling caddy.RegisterNetwork("vsock", fn); or a plugin registered twice via duplicate blank imports / vendored twice under different paths.
Common situations: Name collisions between third-party listener plugins; accidentally importing a plugin package twice (e.g. direct import plus inclusion in a build tag variant); forking a plugin and shipping both fork and original in one binary.
Related errors
- network type %s is already registered
- network type %s is reserved
- module already registered: %s
- command short string is required
- command already registered: %s
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/e3e29d7af0fabf97.
Report an issue: GitHub.