caddyserver/caddy · error

invalid socket file descriptor: %d

Error message

invalid socket file descriptor: %d

What it means

For 'fd'/'fdgram' listening, Caddy wraps the numeric descriptor with os.NewFile and caches it. If the resulting *os.File is nil (the descriptor could not be turned into a file object), this error reports the offending descriptor number. In practice this signals a descriptor that is not open or not a valid socket in the current process.

Source

Thrown at listen.go:61

		func() {
			socketFilesMu.Lock()
			defer socketFilesMu.Unlock()

			socketFdWide := uintptr(socketFd)
			var ok bool

			socketFile, ok = socketFiles[socketFdWide]

			if !ok {
				socketFile = os.NewFile(socketFdWide, lnKey)
				if socketFile != nil {
					socketFiles[socketFdWide] = socketFile
				}
			}
		}()

		if socketFile == nil {
			return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd)
		}
	}

	datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network)
	if datagram {
		sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
			var (
				pc  net.PacketConn
				err error
			)
			if fd {
				pc, err = net.FilePacketConn(socketFile)
			} else {
				pc, err = config.ListenPacket(ctx, network, address)
			}
			if err != nil {
				return nil, err
			}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Confirm the descriptor is actually open in this Caddy process (e.g. ls -l /proc/<pid>/fd).
  2. With systemd, use ListenFDs-style socket activation so the fd is inherited, and reference the correct index.
  3. If not using socket activation, switch to a normal tcp/unix listener instead of 'fd'.
  4. Restart Caddy after changing the supervisor's passed-socket set so numbering matches.

Example fix

// before (fd 3 not actually passed)
{
  listen fd 3
}
// after
{
  listen unix/run/caddy.sock
}
Defensive patterns

Strategy: validation

Validate before calling

func fdOpen(fd uint64) bool {
    _, err := unix.FcntlInt(unix.Fstat(int(fd))) // or os.NewFile check
    return err == nil
}

Prevention

When it happens

Trigger: listen fd with a number that is not an open descriptor in the Caddy process — e.g. it was closed, belongs to another process, or the value is out of range for open descriptors. os.NewFile returning nil for a bad fd is the trigger.

Common situations: Hardcoding an fd number that shifts when the supervisor changes; running Caddy without systemd socket activation while the config assumes it; double-use of a descriptor that was already consumed and closed by an earlier config reload.

Related errors


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