{"record":{"id":"3a70f6d91eb6c89b","repo":"juanfont/headscale","slug":"removing-old-socket-file-w","errorCode":null,"errorMessage":"removing old socket file: %w","messagePattern":"removing old socket file: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/app.go","lineNumber":619,"sourceCode":"\n\tgo h.scheduledTasks(scheduleCtx)\n\n\t// Prepare group for running listeners\n\terrorGroup := new(errgroup.Group)\n\n\tctx := context.Background()\n\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\n\t//\n\t//\n\t// Set up LOCAL listeners\n\t//\n\n\terr = h.ensureUnixSocketIsAbsent()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"removing old socket file: %w\", err)\n\t}\n\n\tsocketDir := filepath.Dir(h.cfg.UnixSocket)\n\n\terr = util.EnsureDir(socketDir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"setting up unix socket: %w\", err)\n\t}\n\n\tsocketListener, err := new(net.ListenConfig).Listen(context.Background(), \"unix\", h.cfg.UnixSocket)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"setting up socket: %w\", err)\n\t}\n\n\t// Change socket permissions\n\tif err := os.Chmod(h.cfg.UnixSocket, h.cfg.UnixSocketPermission); err != nil { //nolint:noinlineerr\n\t\treturn fmt.Errorf(\"changing socket permission: %w\", err)\n\t}","sourceCodeStart":601,"sourceCodeEnd":637,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/app.go#L601-L637","documentation":"Thrown during server startup when ensureUnixSocketIsAbsent() (hscontrol/app.go:410) cannot clear the path configured as unix_socket. The function stats the path and, if it exists (leftover socket, regular file, or directory), removes it with os.Remove; any stat or remove failure is wrapped in this error. It aborts startup before the gRPC/admin socket listener is created.","triggerScenarios":"unix_socket path exists and os.Remove fails: the path is a directory (EISDIR), the parent directory is not writable by the headscale process (EACCES), or an unreachable NFS/FUSE mount returns EIO on stat/remove. Also triggered if another running headscale instance holds a socket at the same path and the filesystem denies removal.","commonSituations":"Running headscale as root first (socket owned by root under /var/run/headscale) and then restarting as an unprivileged user; a leftover socket after an unclean shutdown combined with wrong directory ownership; unix_socket changed to a path whose parent is root-owned; accidentally pointing unix_socket at a directory.","solutions":["Check what occupies the path: ls -l <unix_socket_path>; if it is a stale socket from a dead process, remove it manually: rm <unix_socket_path>.","Fix ownership of the socket's parent directory so the runtime user can write and remove: chown <user> $(dirname <unix_socket_path>).","Ensure only one headscale instance runs with this socket: pgrep -a headscale, and stop the duplicate (e.g. systemctl stop headscale) before restarting.","If the path is a directory or mount point, set unix_socket in config to a file path inside a writable directory (e.g. /var/run/headscale/headscale.sock)."],"exampleFix":"# before (config.yaml)\nunix_socket: /var/run/headscale   # a directory -> os.Remove fails with EISDIR\n\n# after\nunix_socket: /var/run/headscale/headscale.sock\n# and ensure ownership:\n# chown -R headscale:headscale /var/run/headscale","handlingStrategy":"validation","validationCode":"// Before starting Headscale, verify the socket path is clear and removable.\nfunc socketPathReady(p string) bool {\n    fi, err := os.Stat(p)\n    if errors.Is(err, os.ErrNotExist) {\n        return true\n    }\n    if err != nil {\n        return false\n    }\n    return fi.Mode()&os.ModeSocket != 0 // stale socket: safe to remove\n}","typeGuard":"func isRemovableSocket(p string) bool {\n    fi, err := os.Lstat(p)\n    return err == nil && fi.Mode()&os.ModeSocket != 0 && fi.Mode().IsRegular() == false\n}","tryCatchPattern":"if err := h.Serve(); err != nil {\n    var se *os.PathError\n    if errors.As(err, &se) && strings.Contains(se.Error(), \"removing old socket file\") {\n        // stale/socket-dir issue: report and exit, do not retry in a loop\n    }\n}","preventionTips":["Give each headscale instance a unique unix_socket path.","Pre-create the socket directory with correct ownership before start (RuntimeDirectory= in systemd).","Never point unix_socket at a directory or network mount.","In containers, mount a tmpfs at the socket directory."],"tags":["unix-socket","filesystem","startup","permissions"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}