caddyserver/caddy · critical
network type 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.
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 is already registered
- network type is reserved
- module already registered
- command already registered
- command short string is required
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/e3e29d7af0fabf97.
Report an issue: GitHub.
Appendix: 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)