k3s-io/k3s · critical

token is required to join a cluster

Error message

token is required to join a cluster

What it means

During bootstrap, when a managed database (sqlite or embedded etcd) exists but is not initialized and a JoinURL is configured, k3s requires a token to pull bootstrap data from the remote server. Control-plane nodes take this path on every startup, so an empty token is fatal: the node cannot anonymously join a cluster.

Source

Thrown at pkg/cluster/bootstrap.go:124

			// cluster they need to delete the database.
			logrus.Infof("Managed %s cluster bootstrap already complete and initialized", c.managedDB.EndpointName())
			// This is a workaround for an issue that can be caused by terminating the cluster bootstrap before
			// etcd is promoted from learner. Odds are we won't need this info, and we don't want to fail startup
			// due to failure to retrieve it as this will break cold cluster restart, so we ignore any errors.
			if c.config.JoinURL != "" && c.config.Token != "" {
				c.clientAccessInfo, _ = clientaccess.ParseAndValidateToken(c.config.JoinURL, c.config.Token, opts...)
			}
			return false, true, nil
		} else if c.config.JoinURL == "" {
			// Not initialized, not joining - must be initializing (cluster-init)
			logrus.Infof("Managed %s cluster initializing", c.managedDB.EndpointName())
			return false, false, nil
		}

		// Not initialized, but have a Join URL - fail if there's no token; if there is then validate it.
		// Note that this is the path taken by control-plane-only nodes every startup, as they have a non-nil managedDB that is never initialized.
		if c.config.Token == "" {
			return false, false, errors.New("token is required to join a cluster")
		}

		// Fail if the token isn't syntactically valid, or if the CA hash on the remote server doesn't match
		// the hash in the token. The password isn't actually checked until later when actually bootstrapping.
		info, err := clientaccess.ParseAndValidateToken(c.config.JoinURL, c.config.Token, opts...)
		if err != nil {
			return false, false, errors.WithMessage(err, "failed to validate token")
		}
		c.clientAccessInfo = info

		if c.config.DisableETCD {
			logrus.Infof("Managed %s disabled on this node", c.managedDB.EndpointName())
		} else {
			logrus.Infof("Managed %s cluster not yet initialized", c.managedDB.EndpointName())
		}
	}

	// No errors and no bootstrap stamp, need to bootstrap.

View on GitHub (pinned to 6ba341e396)

Solutions

  1. Pass the shared cluster token via --token or K3S_TOKEN (same value as the seed server's /var/lib/rancher/k3s/server/token or a `k3s token create` output)
  2. Verify the token actually reaches the process: `systemctl show k3s -p Environment` and the unit drop-ins
  3. If this node was meant to seed a new cluster, remove --server so it initializes instead of joins

Example fix

# before
k3s server --server https://10.0.0.10:6443

# after
K3S_TOKEN=<cluster-token> k3s server --server https://10.0.0.10:6443
Defensive patterns

Strategy: validation

Validate before calling

// fail fast before cluster bootstrap
if c.config.JoinURL != "" && strings.TrimSpace(c.config.Token) == "" && !c.storageReady() {
    return errors.New("token is required to join a cluster: set K3S_TOKEN/--token")
}

Type guard

func hasJoinCredentials(joinURL, token string) bool {
    return joinURL == "" || strings.TrimSpace(token) != ""
}

Try / catch

if err != nil {
    if strings.Contains(err.Error(), "token is required to join a cluster") {
        // surface an actionable message and stop retrying until the secret is populated
        return fmt.Errorf("K3S_TOKEN missing; populate the token secret and restart")
    }
    return err
}

Prevention

When it happens

Trigger: `k3s server --server https://10.0.0.10:6443` with no --token/K3S_TOKEN and no token in local storage; adding a second (HA) server to a cluster without passing the shared cluster token.

Common situations: New control-plane nodes in embedded-etcd HA clusters where the operator assumes only agents need the token; K3S_TOKEN empty in the systemd unit; token secret not yet populated when the server pod starts.

Related errors


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