hashicorp/nomad · error
service %q is duplicate
Error message
service %q is duplicate
What it means
Within one task group, two services may not share the same name AND port label. Nomad keys registered services by name+port; a duplicate would produce ambiguous Consul registrations, so validation rejects it with this error. The dedup map `knownServices[name+portLabel]` detects the second occurrence.
Source
Thrown at nomad/structs/structs.go:8496
knownServices := make(map[string]struct{})
for i, service := range t.Services {
if err := service.Validate(); err != nil {
outer := fmt.Errorf("service[%d] %+q validation failed: %s", i, service.Name, err)
mErr.Errors = append(mErr.Errors, outer)
}
if service.AddressMode == AddressModeAlloc {
mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q cannot use address_mode=\"alloc\", only services defined in a \"group\" block can use this mode", service.Name))
}
if service.AddressMode == AddressModeAllocIPv6 {
mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q cannot use address_mode=\"alloc_ipv6\", only services defined in a \"group\" block can use this mode", service.Name))
}
// Ensure that services with the same name are not being registered for
// the same port
if _, ok := knownServices[service.Name+service.PortLabel]; ok {
mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q is duplicate", service.Name))
}
knownServices[service.Name+service.PortLabel] = struct{}{}
if service.PortLabel != "" {
if service.AddressMode == "driver" {
// Numeric port labels are valid for address_mode=driver
_, err := strconv.Atoi(service.PortLabel)
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 {View on GitHub (pinned to 482b49bf1a)
Solutions
- Rename one of the duplicate service blocks to a unique name (e.g. suffix with the task name).
- Point one duplicate at a different port label if they were meant to expose distinct endpoints.
- Delete the redundant service stanza if it is a copy-paste artifact.
- Search the job file for repeated `service { name = ...` entries sharing both name and port.
Example fix
// before
group "web" {
service { name = "http"; port = "8080" }
service { name = "http"; port = "8080" }
}
// after
group "web" {
service { name = "http"; port = "8080" }
service { name = "http-admin"; port = "8081" }
} Defensive patterns
Strategy: validation
Validate before calling
seen := map[string]bool{}
for _, tg := range job.TaskGroups {
for _, t := range tg.Tasks {
for _, s := range t.Services {
key := s.Name + s.PortLabel
if seen[key] {
return fmt.Errorf("duplicate service %q on port %q in group %q", s.Name, s.PortLabel, tg.Name)
}
seen[key] = true
}
}
} Prevention
- Suffix service names with the task name when generating jobs from templates
- Search job files for repeated service names before submission
- Remember uniqueness is name+port; same name on different ports is allowed
When it happens
Trigger: Submitting a job whose group (including all nested tasks) defines two service blocks with identical `name` and identical `port`/port_label; note the key is the concatenation name+portLabel, so same name on different ports is allowed.
Common situations: Template-generated jobs that emit a service stanza per task with hardcoded names; copy-pasting a service block and forgetting to change the name; two tasks in one group exposing the same port with the same service name.
Related errors
- check %q is duplicate
- error creating bootstrap configuration for Connect proxy sid
- client stopped and may not longer create config entries
- non-default Consul cluster requires Nomad Enterprise
- expose may only be set for Consul service checks
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/f491e827d07fc368.
Report an issue: GitHub.