hashicorp/nomad · error

ACL bootstrap already done

Error message

ACL bootstrap already done

What it means

Nomad allows ACL bootstrap exactly once; it records an 'acl_token_bootstrap' IndexEntry. This error is returned when a bootstrap is attempted again with resetIndex == 0 while that entry already exists. It is an intentional guard, not a bug.

Source

Thrown at nomad/state/state_store.go:6588

	}

	// Return the reset index if we've already bootstrapped
	return false, out.(*IndexEntry).Value, nil
}

// BootstrapACLTokens is used to create an initial ACL token.
func (s *StateStore) BootstrapACLTokens(msgType structs.MessageType, index uint64, resetIndex uint64, token *structs.ACLToken) error {
	txn := s.db.WriteTxnMsgT(msgType, index)
	defer txn.Abort()

	// Check if we have already done a bootstrap
	existing, err := txn.First("index", "id", "acl_token_bootstrap")
	if err != nil {
		return fmt.Errorf("bootstrap check failed: %v", err)
	}
	if existing != nil {
		if resetIndex == 0 {
			return fmt.Errorf("ACL bootstrap already done")
		} else if resetIndex != existing.(*IndexEntry).Value {
			return fmt.Errorf("Invalid reset index for ACL bootstrap")
		}
	}

	// Update the Create/Modify time
	token.CreateIndex = index
	token.ModifyIndex = index

	// Insert the token
	if err := txn.Insert("acl_token", token); err != nil {
		return fmt.Errorf("upserting token failed: %v", err)
	}

	// Update the indexes table, prevents future bootstrap until reset
	if err := txn.Insert("index", &IndexEntry{"acl_token", index}); err != nil {
		return fmt.Errorf("index update failed: %v", err)
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Use the already-created initial management token instead of re-bootstrapping
  2. Use `nomad acl bootstrap -reset=<index>` with the restore index if the token was lost
  3. Recover the token via the documented recover flow (root-era: use a management token to create new ones)
  4. If the token is irrecoverable, follow Nomad's ACL bootstrap reset procedure documented in the API

Example fix

// before
nomad acl bootstrap  // second run, fails
// after
nomad acl bootstrap -reset=14  // reset index from backup metadata
Defensive patterns

Strategy: try-catch

Validate before calling

list, err := client.ACL().TokensList(nil)
if err == nil && len(list) > 0 { return fmt.Errorf("bootstrap already completed; use existing management token") }

Type guard

func isAlreadyBootstrapped(err error) bool { return err != nil && err.Error() == "ACL bootstrap already done" }

Try / catch

token, _, err := client.ACL().Bootstrap(nil)
if err != nil && err.Error() == "ACL bootstrap already done" {
    return nil // expected on re-runs; fetch token from secret manager
} else if err != nil {
    return err
}

Prevention

When it happens

Trigger: Running `nomad acl bootstrap` a second time without a reset index; calling UpsertACLTokens with an empty BootstrapSecretID reset.

Common situations: Operators forgetting the initial token was already created; scripted provisioning re-running bootstrap; cluster restored from backup already containing the bootstrap entry.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/c9862577cf74b7f5. Report an issue: GitHub.