{"record":{"id":"3204864f442abd34","repo":"juanfont/headscale","slug":"setting-up-socket-w","errorCode":null,"errorMessage":"setting up socket: %w","messagePattern":"setting up socket: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/app.go","lineNumber":631,"sourceCode":"\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}\n\n\t// The Huma v1 API mux matches full /api/v1/... paths and is shared by\n\t// the local unix socket (served without authentication, local trust)\n\t// and the remote TCP router (served behind the API-key middleware).\n\thumaMux, _ := apiv1.Handler(apiv1.Backend{\n\t\tState:  h.state,\n\t\tChange: h.Change,\n\t\tCfg:    h.cfg,\n\t})\n\n\t// The Headscale v2 API. Served behind Basic/Bearer auth on the remote\n\t// listener, and over the local unix socket (local trust) so the CLI can","sourceCodeStart":613,"sourceCodeEnd":649,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/app.go#L613-L649","documentation":"Returned when net.ListenConfig.Listen(\"unix\", h.cfg.UnixSocket) fails at hscontrol/app.go:631, i.e. the operating system refused to bind a Unix domain socket at the configured path. Despite the generic wording, this is specifically the bind of the local admin/gRPC socket, not the TCP listener. The wrapped error is the raw errno from bind(2).","triggerScenarios":"The path was recreated between the removal step and bind (race with another process); path length exceeds the ~108-byte sun_path limit (EINVAL/ENAMETOOLONG); the parent directory is not writable (EACCES); the filesystem does not support Unix sockets, e.g. unix_socket on an NFS/SMB/vBoxsf mount (EINVAL/ENODEV); a file with that name appeared and bind fails with EADDRINUSE.","commonSituations":"Two headscale instances starting concurrently with the same socket; unix_socket set to a long path (deep tmpdir in CI or macOS runner paths); socket placed on a network mount or a Windows bind-mount in Docker; a non-socket file created at the path by a script after cleanup.","solutions":["Re-run: in most race/stale-file cases the next start succeeds because ensureUnixSocketIsAbsent removes the offender; if not, manually rm the socket path.","Shorten the socket path under 108 bytes total (e.g. /var/run/headscale/headscale.sock instead of deeply nested tmp dirs).","Ensure the runtime user has write access to the socket directory (see also the 'setting up unix socket' error for the directory itself).","Move the socket off network/unsupported filesystems onto local disk or a tmpfs."],"exampleFix":"# before\nunix_socket: /mnt/shared/nfs/headscale/headscale.sock   # NFS: bind fails\n\n# after\nunix_socket: /var/run/headscale/headscale.sock","handlingStrategy":"validation","validationCode":"// Keep the unix socket path within the kernel sun_path limit.\nfunc socketPathValid(p string) bool { return len(p) < 108 }\n\n// Fail fast if the path is on a filesystem that cannot host sockets.\nfunc probeUnixSocketFS(dir string) error {\n    probe := filepath.Join(dir, \".probe.sock\")\n    l, err := net.Listen(\"unix\", probe)\n    if err != nil { return err }\n    l.Close()\n    return os.Remove(probe)\n}","typeGuard":null,"tryCatchPattern":"if err := h.Serve(); err != nil {\n    if errors.Is(err, syscall.EADDRINUSE) || strings.Contains(err.Error(), \"setting up socket\") {\n        // another instance or unsupported fs: stop instead of retry-looping\n    }\n}","preventionTips":["Ensure a single headscale process per socket path (systemd is the natural mutex).","Use short, local-filesystem socket paths.","Avoid NFS/SMB bind mounts for the socket directory."],"tags":["unix-socket","network","startup","filesystem"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}