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

  1. Add a network { port "<label>" {} } stanza to the task group matching the check's port_label.
  2. Correct the port_label spelling to match an existing group network port.
  3. 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

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


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