hashicorp/nomad · error

check %q is duplicate

Error message

check %q is duplicate

What it means

Within a single service, checks must have unique names. Nomad iterates service.Checks with a knownChecks set keyed by check.Name and rejects a second check with the same name, since duplicate names make Consul health checks ambiguous and unreferenceable.

Source

Thrown at nomad/structs/structs.go:8522

				if err != nil {
					// Not a numeric port label, add it to list to check
					addServicePort(service.PortLabel, service.Name)
				}
			} else {
				addServicePort(service.PortLabel, service.Name)
			}
		}

		// connect block is only allowed on group level
		if service.Connect != nil {
			mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q cannot have \"connect\" block, only services defined in a \"group\" block can", service.Name))
		}

		// Ensure that check names are unique and have valid ports
		knownChecks := make(map[string]struct{})
		for _, check := range service.Checks {
			if _, ok := knownChecks[check.Name]; ok {
				mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q is duplicate", check.Name))
			}
			knownChecks[check.Name] = struct{}{}

			if check.AddressMode == AddressModeAlloc {
				mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q cannot use address_mode=\"alloc\", only checks defined in a \"group\" service block can use this mode", service.Name))
			}

			if check.AddressMode == AddressModeAllocIPv6 {
				mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q cannot use address_mode=\"alloc_ipv6\", only checks defined in a \"group\" service block can use this mode", service.Name))
			}

			if !check.RequiresPort() {
				// No need to continue validating check if it doesn't need a port
				continue
			}

			effectivePort := check.PortLabel
			if effectivePort == "" {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Give one of the duplicate checks a distinct name, e.g. name = "http-alive" vs "tcp-alive".
  2. Remove the redundant check if it duplicates an existing one.
  3. If different check types are intended, verify each check has a unique, descriptive name.

Example fix

// before
service {
  name = "web"
  check { name = "alive"; type = "http" }
  check { name = "alive"; type = "tcp" }
}

// after
service {
  name = "web"
  check { name = "http-alive"; type = "http" }
  check { name = "tcp-alive"; type = "tcp" }
}
Defensive patterns

Strategy: validation

Validate before calling

for _, svc := range allServices {
  names := map[string]bool{}
  for _, c := range svc.Checks {
    if names[c.Name] {
      return fmt.Errorf("service %q: duplicate check %q", svc.Name, c.Name)
    }
    names[c.Name] = true
  }
}

Prevention

When it happens

Trigger: Submitting a job where one service block declares two `check` stanzas with the same `name` (including checks relying on the default name derivation).

Common situations: Copy-pasting a check stanza to add a second port/endpoint and forgetting to rename it; templated job generation emitting identical check names per protocol (http vs tcp) without renaming.

Related errors


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