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

  1. Set TMPDIR to an exec-allowed, local path (mount without noexec) and re-run
  2. Confirm `go env GOOS GOARCH` matches the host before running cmd/dev
  3. 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

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


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/f9e6748691ffadb7. Report an issue: GitHub.