tailscale/tailscale · error

failed writing systemd user service: %w

Error message

failed writing systemd user service: %w

What it means

Returned by installSystemd when os.WriteFile fails writing the generated unit to <configDir>/systemd/user/tailscale-systray.service with mode 0755. By this point the ExecStart= line has already been rewritten to the resolved tailscale binary path plus 'systray'. The write is the last step before the success message, so failure here leaves no partial unit on disk (WriteFile is atomic-ish: it truncates then writes).

Source

Thrown at client/systray/startup-creator.go:80

	configDir, err := os.UserConfigDir()
	if err != nil {
		homeDir, err := os.UserHomeDir()
		if err != nil {
			return fmt.Errorf("unable to locate user home: %w", err)
		}
		configDir = filepath.Join(homeDir, ".config")
	}

	systemdDir := filepath.Join(configDir, "systemd", "user")
	if err := os.MkdirAll(systemdDir, 0o755); err != nil {
		return fmt.Errorf("failed creating systemd user dir: %w", err)
	}

	serviceFile := filepath.Join(systemdDir, "tailscale-systray.service")

	if err := os.WriteFile(serviceFile, output.Bytes(), 0o755); err != nil {
		return fmt.Errorf("failed writing systemd user service: %w", err)
	}

	fmt.Printf("Successfully installed systemd service to: %s\n", serviceFile)
	fmt.Println("To enable and start the service, run:")
	fmt.Println("  systemctl --user daemon-reload")
	fmt.Println("  systemctl --user enable --now tailscale-systray")

	return nil
}

func installFreedesktop() error {
	tmpDir, err := os.MkdirTemp("", "tailscale-systray")
	if err != nil {
		return fmt.Errorf("unable to make tmpDir: %w", err)
	}
	defer os.RemoveAll(tmpDir)

	// Install icon, and use it if it works, and if not change to some generic

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. If a root-owned service file exists from an earlier sudo run, remove or chown it: sudo rm ~/.config/systemd/user/tailscale-systray.service.
  2. Confirm the directory is writable by the current user (ls -ld ~/.config/systemd/user).
  3. Free disk space if the write fails with a no-space error.
  4. Prefer running the installer as the same user that will own the systemd --user unit.

Example fix

# before
$ ./tailscale-systray-setup
failed writing systemd user service: ... permission denied

# after
$ ls -l ~/.config/systemd/user/tailscale-systray.service
-rwxr-xr-x 1 root root ... tailscale-systray.service
$ sudo rm ~/.config/systemd/user/tailscale-systray.service
$ ./tailscale-systray-setup
Defensive patterns

Strategy: try-catch

Validate before calling

svc := filepath.Join(configDir, "systemd", "user", "tailscale-systray.service")
if fi, err := os.Stat(svc); err == nil && fi.Mode().Perm() != 0o755 {
    // possible root-owned leftover: remove before reinstall
}

Type guard

func isPermissionErr(err error) bool { return errors.Is(err, fs.ErrPermission) }

Try / catch

if err := systray.InstallStartupScript("systemd"); err != nil {
    if errors.Is(err, fs.ErrPermission) && strings.Contains(err.Error(), "writing systemd") {
        // remove root-owned unit file, then retry
    }
}

Prevention

When it happens

Trigger: An existing tailscale-systray.service owned by root (from a previous sudo install) that the current user cannot overwrite; read-only home filesystem; disk full; permission denial from mandatory access control on ~/.config/systemd/user.

Common situations: Re-running the installer as a normal user after a first run under sudo left root-owned files; systemd user unit directories under management (immutable/overlayfs); full disks in VMs.

Related errors


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/033f36ce154c9522. Report an issue: GitHub.