caddyserver/caddy · critical

network type %s is reserved

Error message

network type %s is reserved

What it means

RegisterNetwork lets plugins register custom listener network types (called from init()). Standard network names — tcp/tcp4/tcp6, udp/udp4/udp6, unix/unixpacket/unixgram, ip:/ip4:/ip6: prefixes, fd, fdgram — are reserved because Caddy handles them natively. Attempting to register one panics to prevent hijacking core networking.

Source

Thrown at listeners.go:673

	} else if fcql.closed.CompareAndSwap(1, 2) {
		_, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
	}
	return nil
}

// 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))

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Choose a non-standard, namespaced network name such as "myplugin:vsock" or "unixgram-custom"
  2. If you need to intercept standard networks, hook listener wrapping (Server.ListenCallbacks / caddy.Listen) instead of RegisterNetwork
  3. Check the reserved list in listeners.go before naming your network type

Example fix

// before
func init() {
    caddy.RegisterNetwork("tcp", myListenerFunc) // reserved
}
// after
func init() {
    caddy.RegisterNetwork("myplugin:tcp", myListenerFunc)
}
Defensive patterns

Strategy: validation

Validate before calling

var reservedNetworks = map[string]bool{
    "tcp": true, "tcp4": true, "tcp6": true,
    "udp": true, "udp4": true, "udp6": true,
    "unix": true, "unixpacket": true, "unixgram": true,
    "fd": true, "fdgram": true,
}

func isReservedNetwork(n string) bool {
    n = strings.ToLower(strings.TrimSpace(n))
    if reservedNetworks[n] {
        return true
    }
    return strings.HasPrefix(n, "ip:") || strings.HasPrefix(n, "ip4:") || strings.HasPrefix(n, "ip6:")
}

Prevention

When it happens

Trigger: Calling caddy.RegisterNetwork("tcp", fn), RegisterNetwork("unix", fn), RegisterNetwork("fd", fn), or any name with an ip:/ip4:/ip6: prefix from a plugin's init().

Common situations: Writing a listener plugin (e.g. custom socket types, systemd fd passing) and picking a name that collides with a standard network; assuming you can wrap/replace Caddy's TCP listener this way.

Related errors


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