juanfont/headscale · error
starting headscale: %w
Error message
starting headscale: %w
What it means
"starting headscale: %w" at cmd/dev/main.go:150 wraps serve.Start() of the freshly built `<tmpDir>/headscale serve -c <tmpDir>/config.yaml` in cmd/dev. Start() only fails if the process cannot be spawned at all (exec format error, permission denied, binary missing); runtime failures of the server show up later via the health check or serve.Wait. The child inherits stdout/stderr, so any exec-level message is on the console.
Source
Thrown at cmd/dev/main.go:150
build := exec.CommandContext(ctx, "go", "build", "-o", hsBin, "./cmd/headscale")
build.Stdout = os.Stdout
build.Stderr = os.Stderr
err = build.Run()
if err != nil {
return fmt.Errorf("building headscale: %w", err)
}
// Start headscale serve.
fmt.Println("Starting headscale server...")
serve := exec.CommandContext(ctx, hsBin, "serve", "-c", configPath)
serve.Stdout = os.Stdout
serve.Stderr = os.Stderr
err = serve.Start()
if err != nil {
return fmt.Errorf("starting headscale: %w", err)
}
// Wait for server to be ready.
healthURL := fmt.Sprintf("http://127.0.0.1:%d/health", *port)
err = waitForHealth(ctx, healthURL, 30*time.Second)
if err != nil {
return fmt.Errorf("waiting for headscale: %w", err)
}
// Create user.
fmt.Println("Creating user and pre-auth key...")
userJSON, err := runHS(ctx, hsBin, configPath, "users", "create", "dev", "-o", "json")
if err != nil {
return fmt.Errorf("creating user: %w", err)
}
View on GitHub (pinned to 565fd254d0)
Solutions
- Set TMPDIR to an exec-allowed, local path (mount without noexec) and re-run
- Confirm `go env GOOS GOARCH` matches the host before running cmd/dev
- Disable/allowlist the binary in security software if it quarantines new executables
Example fix
# before (tmpdir on a noexec mount) go run ./cmd/dev # after mkdir -p "$HOME/tmp" && TMPDIR="$HOME/tmp" go run ./cmd/dev
Defensive patterns
Strategy: try-catch
Validate before calling
// ensure the binary is executable on this host before Start
if fi, err := os.Stat(hsBin); err != nil || fi.Mode()&0o111 == 0 {
return errors.New("dev binary missing or not executable; check TMPDIR mount flags (noexec?)")
} Try / catch
if err := serve.Start(); err != nil {
// only spawn-time failures land here: noexec TMPDIR, wrong GOARCH, deleted binary
return fmt.Errorf("starting headscale: %w", err)
} Prevention
- Ensure TMPDIR is not mounted noexec
- Match go env GOOS/GOARCH to the host when using the dev tool
When it happens
Trigger: The built binary was deleted or truncated before Start (antivirus/tmp reaper); exec.PermissionDenied because the tmpDir mount is noexec; cross-compilation environment producing a binary for the wrong architecture (exec format error).
Common situations: /tmp mounted noexec on hardened distros; building under a mismatched GOARCH; security tooling quarantining freshly compiled binaries.
Related errors
- creating temp dir: %w
- writing config: %w
- headscale exited: %w
- building headscale: %w
- waiting for headscale: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/f9e6748691ffadb7.
Report an issue: GitHub.