hashicorp/nomad · error
unable to determine local service port for service check %s-
Error message
unable to determine local service port for service check %s->%s->%s
What it means
When mutating a job to add expose paths, Nomad must compute the local port that the Envoy proxy should listen on for an exposed check. It resolves the port via the group's network port label; if the label is not found in tg.Networks.Port() and the port label string itself is not a numeric port, no local port can be determined and the mutation fails.
Source
Thrown at nomad/job_endpoint_hook_expose_check.go:216
To: -1,
}
tg.Networks[0].DynamicPorts = append(tg.Networks[0].DynamicPorts, port)
check.PortLabel = port.Label
}
// Determine the local service port (i.e. what port the service is actually
// listening to inside the network namespace).
//
// Similar logic exists in getAddress of client.go which is used for
// creating check & service registration objects.
//
// The difference here is the address is predestined to be localhost since
// it is binding inside the namespace.
var port int
if mapping := tg.Networks.Port(s.PortLabel); mapping.Value <= 0 { // try looking up by port label
if port, _ = strconv.Atoi(s.PortLabel); port <= 0 { // then try direct port value
return nil, fmt.Errorf(
"unable to determine local service port for service check %s->%s->%s",
tg.Name, s.Name, check.Name,
)
}
} else {
port = mapping.Value
}
// The Path, Protocol, and PortLabel are just copied over from the service
// check definition.
return &structs.ConsulExposePath{
Path: check.Path,
Protocol: check.Protocol,
LocalPathPort: port,
ListenerPort: check.PortLabel,
}, nil
}
View on GitHub (pinned to 482b49bf1a)
Solutions
- Add a network { port "<label>" {} } stanza to the task group matching the check's port_label.
- Correct the port_label spelling to match an existing group network port.
- Set port_label to a literal numeric port value as a fallback.
Example fix
// before
group "app" {
network { mode = "bridge" } # no port "api" defined
service {
check { expose = true port_label = "api" }
}
}
// after
group "app" {
network {
mode = "bridge"
port "api" {}
}
service {
check { expose = true port_label = "api" }
}
} Defensive patterns
Strategy: validation
Validate before calling
// Verify each exposed check's port_label exists in group network ports or is numeric
labels := map[string]bool{}
for _, n := range tg.Networks {
for _, p := range n.ReservedPorts { labels[p.Label] = true }
for _, p := range n.DynamicPorts { labels[p.Label] = true }
}
for _, s := range tg.Services {
for _, c := range s.Checks {
if c.Expose {
if !labels[s.PortLabel] && !isNumeric(s.PortLabel)
throw new Error(`exposed check on '${s.Name}' uses unresolvable port_label '${s.PortLabel}'`)
}
}
} Type guard
func resolvablePortLabel(tg *api.TaskGroup, label string) bool {
for _, n := range tg.Networks {
for _, p := range append(n.ReservedPorts, n.DynamicPorts...) {
if p.Label == label {
return true
}
}
}
if _, err := strconv.Atoi(label); err == nil {
return true
}
return false
} Try / catch
// golang — Mutate failure surfaces as job submission/validation error
if _, err := client.Jobs().Validate(job); err != nil {
if strings.Contains(err.Error(), "unable to determine local service port") {
// add a network port stanza matching the port_label
}
} Prevention
- Always declare a network { port "<label>" {} } for any port_label used by exposed checks.
- Define ports at the group network level (task-level ports are not visible to the hook).
- Check spelling of port_label against the group network block.
- Use literal ports sparingly; prefer labeled dynamic ports declared explicitly.
When it happens
Trigger: A group service with an expose=true check whose port_label references a dynamic label that does not exist in the group's network block, and is not a literal port number — typically when the group declares no network { port "x" {} } entry or the label is misspelled.
Common situations: Referencing a port defined at task level instead of group network level; typo in port_label; using a label that only exists after dynamic port allocation but declaring networks = [] with bridge mode and no explicit port stanza.
Related errors
- exposed service check %s->%s->%s requires use of sidecar_pro
- exposed service check %s[%s]->%s->%s is not a task-group ser
- envoy must be used as connect sidecar or gateway
- ErrConnectRequireOneNetwork
- ErrConnectInvalidNetworkMode
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/22b0fc741850c6a8.
Report an issue: GitHub.