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
- Use the already-created initial management token instead of re-bootstrapping
- Use `nomad acl bootstrap -reset=<index>` with the restore index if the token was lost
- Recover the token via the documented recover flow (root-era: use a management token to create new ones)
- 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
- Store the initial management token in a secret manager immediately after first bootstrap
- Make bootstrap scripts idempotent by treating this error as success
- Never re-run bootstrap blindly in CI/CD provisioning
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
- bootstrap check failed: %v
- Invalid reset index for ACL bootstrap
- ACL policy not found
- ACL role not found
- detected corrupted token within the state store: missing rol
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/c9862577cf74b7f5.
Report an issue: GitHub.