hashicorp/nomad · error

exposed service check %s[%s]->%s->%s is not a task-group ser

Error message

exposed service check %s[%s]->%s->%s is not a task-group service

What it means

Nomad only allows exposed (expose=true) service checks on task-group-level services backed by a Consul Connect sidecar. A check defined on a service inside a task's service block can never be exposed, because expose paths are wired into the group's Envoy proxy namespace. Validation fails fast with the group, task, service, and check names.

Source

Thrown at nomad/job_endpoint_hook_expose_check.go:122

func tgValidateUseOfCheckExpose(tg *structs.TaskGroup) error {
	// validation for group services (which must use built-in connect proxy)
	for _, s := range tg.Services {
		for _, check := range s.Checks {
			if check.Expose && !s.Connect.HasSidecar() {
				return fmt.Errorf(
					"exposed service check %s->%s->%s requires use of sidecar_proxy",
					tg.Name, s.Name, check.Name,
				)
			}
		}
	}

	// validation for task services (which must not be configured to use Expose)
	for _, t := range tg.Tasks {
		for _, s := range t.Services {
			for _, check := range s.Checks {
				if check.Expose {
					return fmt.Errorf(
						"exposed service check %s[%s]->%s->%s is not a task-group service",
						tg.Name, t.Name, s.Name, check.Name,
					)
				}
			}
		}
	}
	return nil
}

// tgValidateExposeNetworkMode ensures there is exactly 1 network configured for
// the task group, and that it uses "bridge" or "cni/*" mode (i.e. enables network
// namespaces).
func tgValidateExposeNetworkMode(tg *structs.TaskGroup) error {
	if tgUsesExposeCheck(tg) {
		return groupConnectNetworkModeValidate(tg, "connect expose check", false)
	}
	return nil

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Move the service (and its exposed check) out of the task and up to the task group level, adding connect { sidecar_service {} } if needed.
  2. Remove expose = true from the task-service check and use a normal check with a port instead.
  3. Split the app so the exposed dependency is registered as a group service.

Example fix

// before
task "web" {
  service {
    name = "web"
    check { expose = true }
  }
}
// after
group "app" {
  service {
    name = "web"
    connect { sidecar_service {} }
    check { expose = true }
  }
  task "web" { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure no task-level service declares an exposed check
job.TaskGroups.each { tg ->
  tg.Tasks.each { t ->
    (t.Services || []).each { s ->
      (s.Checks || []).each { c ->
        if c.Expose) throw new Error(`exposed check '${c.Name}' found in task '${t.Name}' service '${s.Name}'; move service to group level`)
      }
    }
  }
}

Type guard

func isGroupService(tg *api.TaskGroup, svc *api.Service) bool {
	for _, s := range tg.Services {
		if s.Name == svc.Name {
			return true
		}
	}
	return false
}

Try / catch

// golang
if _, err := client.Jobs().Validate(job); err != nil {
	if strings.Contains(err.Error(), "is not a task-group service") {
		// relocate service block to group level or drop expose
	}
}

Prevention

When it happens

Trigger: Submitting a job where any task-level service (inside task { service { ... } }) declares a check with expose = true, regardless of connect configuration.

Common situations: Developers moving expose-enabled checks from group services into task services during refactoring; mistaking task services for group services in HCL; templated jobs that hoist checks into task blocks.

Related errors


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