juanfont/headscale · critical
setting up unix socket: %w
Error message
setting up unix socket: %w
What it means
Thrown during startup when util.EnsureDir fails to create the parent directory of the configured unix_socket path (socketDir := filepath.Dir(h.cfg.UnixSocket) in hscontrol/app.go:623). EnsureDir does a MkdirAll on that directory; failure means the directory could not be created or already exists with wrong permissions/ownership. Startup aborts before listening.
Source
Thrown at hscontrol/app.go:626
ctx, cancel := context.WithCancel(ctx)
defer cancel()
//
//
// Set up LOCAL listeners
//
err = h.ensureUnixSocketIsAbsent()
if err != nil {
return fmt.Errorf("removing old socket file: %w", err)
}
socketDir := filepath.Dir(h.cfg.UnixSocket)
err = util.EnsureDir(socketDir)
if err != nil {
return fmt.Errorf("setting up unix socket: %w", err)
}
socketListener, err := new(net.ListenConfig).Listen(context.Background(), "unix", h.cfg.UnixSocket)
if err != nil {
return fmt.Errorf("setting up socket: %w", err)
}
// Change socket permissions
if err := os.Chmod(h.cfg.UnixSocket, h.cfg.UnixSocketPermission); err != nil { //nolint:noinlineerr
return fmt.Errorf("changing socket permission: %w", err)
}
// The Huma v1 API mux matches full /api/v1/... paths and is shared by
// the local unix socket (served without authentication, local trust)
// and the remote TCP router (served behind the API-key middleware).
humaMux, _ := apiv1.Handler(apiv1.Backend{
State: h.state,
Change: h.Change,View on GitHub (pinned to 565fd254d0)
Solutions
- Pre-create the socket directory with correct ownership: install -d -o headscale -g headscale /run/headscale (or add RuntimeDirectory=headscale to the systemd unit).
- If in a container, mount a writable tmpfs/volume at the socket directory: docker run ... --tmpfs /var/run/headscale.
- Check for path-component conflicts: a regular file sitting where a directory is needed (ls -ld each component) and remove or relocate it.
- If SELinux is enforcing, set the correct context (restorecon -v on the directory) or adjust the policy for the headscale process.
Example fix
# before: systemd unit without runtime dir, /run/headscale missing -> EACCES [Service] User=headscale # after [Service] User=headscale RuntimeDirectory=headscale RuntimeDirectoryMode=0755
Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight: ensure the socket dir is creatable/writable.
func ensureSocketDir(socketPath string) error {
dir := filepath.Dir(socketPath)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("socket dir not writable: %w", err)
}
return nil
} Try / catch
if err := h.Serve(); err != nil && strings.Contains(err.Error(), "setting up unix socket") {
log.Fatalf("socket directory problem, check ownership of %s: %v", filepath.Dir(cfg.UnixSocket), err)
} Prevention
- Set RuntimeDirectory=headscale in the systemd unit so /run/headscale exists with right owner.
- Mount a writable volume/tmpfs at the socket dir in containers.
- Verify directory ownership in config-management playbooks before starting the service.
When it happens
Trigger: unix_socket is set to a path like /run/headscale/headscale.sock but /run/headscale cannot be created because /run is root-owned and headscale runs unprivileged (EACCES); the target path component exists as a regular file (ENOTDIR); a read-only filesystem (EROFS), commonly containers with a read-only rootfs and no writable volume for the socket.
Common situations: Running headscale in Docker/Kubernetes without a writable volume or tmpfs at the socket path; running as the headscale system user without RuntimeDirectory= set in the systemd unit (so /run/headscale never exists); SELinux denying writes to /run; moving config to /etc but leaving unix_socket pointing somewhere unwritable.
Related errors
- removing old socket file: %w
- changing socket permission: %w
- creating directory failed with permission error
- reading or creating Noise protocol private key: %w
- reading or creating DERP server private key: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/f9ea93529d8f7fcf.
Report an issue: GitHub.