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
- Give one of the duplicate checks a distinct name, e.g. name = "http-alive" vs "tcp-alive".
- Remove the redundant check if it duplicates an existing one.
- 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
- Name every check explicitly and descriptively (e.g. http-probe, tcp-probe)
- When copy-pasting checks, rename immediately
- Enforce check-name uniqueness in job templating code
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
- service %q is duplicate
- check %q cannot use address_mode="alloc", only checks define
- check %q is missing a port
- error getting address for check %q: %v
- Task group %q has %d/%d healthy allocations
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/ac6c9e8585d387e9.
Report an issue: GitHub.