hashicorp/nomad · error

ACL policy not found

Error message

ACL policy not found

What it means

Nomad's state store rejects an ACL role upsert when one of the role's policy links references a policy name that does not exist in the acl_policy table. validateACLRolePolicyLinksTxn iterates role.Policies and does a txn.FirstWatch lookup per link; a nil result means the referenced policy is absent. The write is aborted so roles can never point at nonexistent policies.

Source

Thrown at nomad/state/state_store_acl.go:172

	}

	// Insert the role into the table.
	if err := txn.Insert(TableACLRoles, role); err != nil {
		return false, fmt.Errorf("ACL role insert failed: %v", err)
	}
	return true, nil
}

// validateACLRolePolicyLinksTxn is the same as ValidateACLRolePolicyLinks but
// allows callers to pass their own transaction.
func (s *StateStore) validateACLRolePolicyLinksTxn(txn *txn, role *structs.ACLRole) error {
	for _, policyLink := range role.Policies {
		_, existing, err := txn.FirstWatch("acl_policy", indexID, policyLink.Name)
		if err != nil {
			return fmt.Errorf("ACL policy lookup failed: %v", err)
		}
		if existing == nil {
			return errors.New("ACL policy not found")
		}
	}
	return nil
}

// DeleteACLRolesByID is responsible for batch deleting ACL roles based on
// their ID. It uses a single write transaction for efficiency, however, any
// error means no entries will be committed. An error is produced if a role is
// not found within state which has been passed within the array.
func (s *StateStore) DeleteACLRolesByID(
	msgType structs.MessageType, index uint64, roleIDs []string) error {

	txn := s.db.WriteTxnMsgT(msgType, index)
	defer txn.Abort()

	for _, roleID := range roleIDs {
		if err := s.deleteACLRoleByIDTxn(txn, roleID); err != nil {
			return err

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Create the missing ACL policy first (nomad acl policy apply) so the name in role.Policies matches exactly.
  2. Fix the typo in the role's policy link name and re-apply the role.
  3. Remove the stale policy link from the role if the policy is intentionally gone.
  4. If restoring state, ensure policies are restored before roles.

Example fix

// before: role references policy that does not exist
nomad acl role create -name dev -policy developer-pol  // typo
// after: create the policy (correct name) first, then the role
nomad acl policy apply developer policies/developer.hcl
nomad acl role create -name dev -policy developer
Defensive patterns

Strategy: validation

Validate before calling

// before applying the role, check each policy link exists
for _, p := range role.Policies {
    if _, _, err := client.ACLPolicies().Get(p.Name, nil); err != nil {
        return fmt.Errorf("policy %q must be created before role: %w", p.Name, err)
    }
}

Try / catch

// treat as preconditions-failed on upsert
if err := upsertRole(role); err != nil && strings.Contains(err.Error(), "ACL policy not found") {
    return fmt.Errorf("role %s links unknown policy; create policies first: %w", role.Name, err)
}

Prevention

When it happens

Trigger: Calling the ACL Role Upsert RPC (ACLPolicy.UpsertACLRoles / nomad acl role apply with -policy) where role.Policies contains a name with no matching ACL policy, or restoring a snapshot whose role records predate their policy records.

Common situations: Typo in the policy name in an HCL/JSON role definition; creating a role before its policies; deleting a policy that a role still links to and then re-applying the role; state restore from an older cluster.

Related errors


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