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, nil

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Give your network type a unique namespaced name (e.g. "acme:wire")
  2. Check which plugins are compiled in: caddy list-modules --packages, and remove the duplicate import
  3. 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

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


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