k3s-io/k3s · error

failed to create symlink: %v

Error message

failed to create symlink: %v

What it means

The final step of writeConfigSymlink calls os.Symlink to point the well-known kubeconfig path at the data-dir kubeconfig. Any failure creating the symlink - most commonly the target path already exists again (race with another process), permission on the parent directory, or symlink privilege restrictions - is returned as this error.

Source

Thrown at pkg/server/server.go:533

	envList = append(envList,
		".svc",
		"."+config.ClusterDomain,
		util.JoinIPNets(config.ClusterIPRanges),
		util.JoinIPNets(config.ServiceIPRanges),
	)
	os.Unsetenv("no_proxy")
	return os.Setenv("NO_PROXY", strings.Join(envList, ","))
}

func writeConfigSymlink(kubeconfig, kubeconfigSymlink string) error {
	if err := os.Remove(kubeconfigSymlink); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("failed to remove %s file: %v", kubeconfigSymlink, err)
	}
	if err := os.MkdirAll(filepath.Dir(kubeconfigSymlink), 0755); err != nil {
		return fmt.Errorf("failed to create path for symlink: %v", err)
	}
	if err := os.Symlink(kubeconfig, kubeconfigSymlink); err != nil {
		return fmt.Errorf("failed to create symlink: %v", err)
	}
	return nil
}

func isSymlink(config string) bool {
	if fi, err := os.Lstat(config); err == nil && (fi.Mode()&os.ModeSymlink == os.ModeSymlink) {
		return true
	}
	return false
}

func setNodeLabelsAndAnnotations(ctx context.Context, nodes v1.NodeClient, config *Config) error {
	if config.DisableAgent || config.ControlConfig.DisableAPIServer {
		return nil
	}

	patcher := util.NewPatcher[*corev1.Node](nodes)
	return wait.PollUntilContextCancel(ctx, time.Second, true, func(ctx context.Context) (bool, error) {

View on GitHub (pinned to 6ba341e396)

Solutions

  1. Check for and clear the target path: ls -la /etc/rancher/k3s/ and remove a stale k3s.yaml, then restart k3s.
  2. Ensure only one k3s instance manages a given /etc/rancher directory (check for duplicate systemd units or containers).
  3. On Windows, run from an elevated shell or enable Developer Mode so unprivileged symlink creation is allowed.
  4. Confirm write permission on the parent directory and absence of MAC-policy denials in the wrapped error.

Example fix

# before: stale target left by a racing instance
ls -l /etc/rancher/k3s/k3s.yaml  # regular file, k3s fails at os.Symlink

# after
sudo rm /etc/rancher/k3s/k3s.yaml && sudo systemctl restart k3s
Defensive patterns

Strategy: retry

Validate before calling

// Check target is absent or already the right symlink before start
if fi, err := os.Lstat(target); err == nil {
    if fi.Mode()&os.ModeSymlink != 0 { /* already linked, ok */ } else {
        os.Remove(target) // clear regular file left by another tool
    }
}

Try / catch

// Handle EEXIST-style races by clearing and retrying once
if err := writeConfigSymlink(kc, sym); err != nil {
    if strings.Contains(err.Error(), "failed to create symlink") {
        os.Remove(sym)
        err = writeConfigSymlink(kc, sym) // single deterministic retry
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Server start where os.Symlink(kubeconfig, kubeconfigSymlink) fails: another k3s instance/loop recreated the path between the Remove and Symlink calls, the parent dir is not writable, or on Windows without the SeCreateSymbolicLinkPrivilege (developer mode / admin requirement).

Common situations: Two k3s servers sharing or racing over the same /etc/rancher path; system hardening that revoked write permission right after startup began; running k3s on Windows hosts without developer mode; antivirus/EDR briefly locking the new symlink.

Related errors


AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15). Data as JSON: /api/errors/4e691e72561b0d5a. Report an issue: GitHub.