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
- Choose a non-standard, namespaced network name such as "myplugin:vsock" or "unixgram-custom"
- If you need to intercept standard networks, hook listener wrapping (Server.ListenCallbacks / caddy.Listen) instead of RegisterNetwork
- 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
- Namespace custom network names (vendor:transport)
- Register networks only in init()
- Read listeners.go's reserved list before adding a type
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
- network type %s is already registered
- network type %s is already registered
- starting HTTP/3 QUIC listener: %v
- command short string is required
- command already registered: %s
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/1185cd1d1e94bbd7.
Report an issue: GitHub.