{"record":{"id":"c948c80b5be030b0","repo":"juanfont/headscale","slug":"changing-socket-permission-w","errorCode":null,"errorMessage":"changing socket permission: %w","messagePattern":"changing socket permission: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/app.go","lineNumber":636,"sourceCode":"\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\n\t// manage OAuth clients through the same v2 keys handler the Tailscale\n\t// ecosystem uses.\n\thumaV2Mux, _ := apiv2.Handler(apiv2.Backend{\n\t\tState:  h.state,\n\t\tChange: h.Change,","sourceCodeStart":618,"sourceCodeEnd":654,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/app.go#L618-L654","documentation":"Returned when os.Chmod(h.cfg.UnixSocket, h.cfg.UnixSocketPermission) fails right after the Unix socket is bound (hscontrol/app.go:636). The chmod applies the configured unix_socket_permission mode to the freshly created socket. Failure means the socket file could not be modified, which is nearly always a race or a filesystem limitation rather than a bad mode value.","triggerScenarios":"The socket file disappeared between Listen and Chmod (another process or a second headscale removed it -> ENOENT); the socket sits on a filesystem that does not support chmod semantics; the process lacks ownership rights over the file it just created (unusual, e.g. after a setfsuid transition or idmapped mounts in containers).","commonSituations":"Two headscale instances with identical configs starting at once (one removes/chmods the other's socket); cleanup scripts (tmpwatch, container sidecars) deleting sockets aggressively; unusual container storage drivers that break chmod on sockets.","solutions":["Verify only one headscale runs per socket path: pgrep -a headscale; stop duplicates and restart.","Re-run the service; transient ENOENT races resolve on a clean single-instance start.","If a cleanup daemon deletes the socket, exclude the socket directory from it or relocate unix_socket.","On exotic container storage, move the socket to a tmpfs mount (--tmpfs /var/run/headscale)."],"exampleFix":"# before: two units pointing at the same socket\n# headscale.service and headscale2.service both use /var/run/headscale/headscale.sock\n\n# after: give each instance its own socket in config.yaml\nunix_socket: /var/run/headscale/headscale.sock   # instance A\nunix_socket: /var/run/headscale2/headscale.sock  # instance B","handlingStrategy":"retry","validationCode":" // after startup, verify permissions took effect\nfi, err := os.Stat(cfg.UnixSocket)\nif err == nil && fi.Mode().Perm() != cfg.UnixSocketPermission {\n    // log/repair\n}","typeGuard":null,"tryCatchPattern":"if err := h.Serve(); err != nil && strings.Contains(err.Error(), \"changing socket permission\") {\n    // race with concurrent instance: stop the other process and restart once, do not loop","preventionTips":["Run exactly one instance per socket path.","Pre-set sane unix_socket_permission in config so chmod is a no-op of the default.","Keep aggressive tmp-cleanup daemons away from the socket directory."],"tags":["unix-socket","filesystem","permissions","startup","race-condition"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}