{"record":{"id":"ec7e319c26c3e9f7","repo":"caddyserver/caddy","slug":"invalid-socket-file-descriptor-d-ec7e31","errorCode":null,"errorMessage":"invalid socket file descriptor: %d","messagePattern":"invalid socket file descriptor: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"listen_unix.go","lineNumber":130,"sourceCode":"\t\tfunc() {\n\t\t\tsocketFilesMu.Lock()\n\t\t\tdefer socketFilesMu.Unlock()\n\n\t\t\tsocketFdWide := uintptr(socketFd)\n\t\t\tvar ok bool\n\n\t\t\tsocketFile, ok = socketFiles[socketFdWide]\n\n\t\t\tif !ok {\n\t\t\t\tsocketFile = os.NewFile(socketFdWide, lnKey)\n\t\t\t\tif socketFile != nil {\n\t\t\t\t\tsocketFiles[socketFdWide] = socketFile\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\tif socketFile == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid socket file descriptor: %d\", socketFd)\n\t\t}\n\t} else {\n\t\t// wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs\n\t\toldControl := config.Control\n\t\tconfig.Control = func(network, address string, c syscall.RawConn) error {\n\t\t\tif oldControl != nil {\n\t\t\t\tif err := oldControl(network, address, c); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn reusePort(network, address, c)\n\t\t}\n\t}\n\n\tdatagram := slices.Contains([]string{\"udp\", \"udp4\", \"udp6\", \"unixgram\", \"fdgram\"}, network)\n\tif datagram {\n\t\tif fd {\n\t\t\tln, err = net.FilePacketConn(socketFile)","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/listen_unix.go#L112-L148","documentation":"Unix-platform counterpart of listen.go:61 — after parsing the fd number, if os.NewFile returns nil (descriptor invalid/not open in this process), Caddy rejects it with the numeric value in the message.","triggerScenarios":"listen fd N where N is not an open descriptor in the Caddy process on Unix: wrong index for systemd-passed sockets, descriptor already closed by an earlier reload, or fabricated number.","commonSituations":"systemd socket activation where LISTEN_FDS count changed but the config still references an old index; manually hardcoding 3 when nothing was passed; reload cycles that consumed and closed the cached descriptor.","solutions":["Check /proc/<caddy-pid>/fd to see which descriptors exist.","Use systemd socket activation properly and reference the right offset (fds start at 3).","Prefer 'systemd' integration or plain unix/tcp listeners if fd management is error-prone for you."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"func fdUsable(fd int) bool {\n    f := os.NewFile(uintptr(fd), \"probe\")\n    if f == nil { return false }\n    return f != nil // presence in-process implies open\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pass descriptors explicitly via socket activation rather than guessing indexes.","After config reloads that close fds, re-derive fd numbers from the environment."],"tags":["file-descriptor","socket-activation","unix","runtime"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}