hashicorp/nomad · error

exposed service check %s->%s->%s requires use of sidecar_pro

Error message

exposed service check %s->%s->%s requires use of sidecar_proxy

What it means

Nomad's job validation rejects a group service check with expose=true when the service's Connect block has no sidecar_proxy. Exposed checks only work by routing through the Envoy sidecar that Consul Connect injects; without connect { sidecar_service {} } there is no proxy to bind the exposed listener in, so the job is invalid. HasSidecar() is simply `c != nil && c.SidecarService != nil` (nomad/structs/services.go:1286).

Source

Thrown at nomad/job_endpoint_hook_expose_check.go:109

		s.Connect.SidecarService.Proxy.Expose = new(structs.ConsulExposeConfig)
	}
	return s.Connect.SidecarService.Proxy.Expose
}

// containsExposePath returns true if path is contained in paths.
func containsExposePath(paths []structs.ConsulExposePath, path structs.ConsulExposePath) bool {
	return slices.Contains(paths, path)
}

// tgValidateUseOfCheckExpose ensures that any service check in tg making use
// of the expose field is within an appropriate context to do so. The check must
// be a group level check, and must use the builtin envoy proxy.
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,
					)
				}
			}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Add connect { sidecar_service {} } to the group service so Envoy is injected.
  2. Remove expose = true from the check if you don't need it exposed through the proxy.
  3. Move the service to group level if it is currently a task service (task services can never use expose; see error 2346).

Example fix

// before
service {
  name = "api"
  connect {}
  check {
    expose = true
  }
}
// after
service {
  name = "api"
  connect { sidecar_service {} }
  check {
    expose = true
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check HCL/JSON job spec before submit
tg.Services.each { s ->
  s.Checks.each { c ->
    if c.Expose && !(s.Connect != null && s.Connect.SidecarService != null)
      throw new Error("check '${c.Name}' on service '${s.Name}' sets expose but service has no sidecar_proxy")
  }
}

Type guard

func hasConnectSidecar(s *api.Service) bool {
	return s != nil && s.Connect != nil && s.Connect.SidecarService != nil
}

Try / catch

// golang
err := client.Jobs().Validate(job)
if err != nil && strings.Contains(err.Error(), "requires use of sidecar_proxy") {
	// amend job spec: add connect.sidecar_service
}

Prevention

When it happens

Trigger: Submitting (nomad job run / jobs API Validate) a job where a task-group-level service has a check with expose=true but the service's connect block is absent, or is connect { sidecar_service { ... } } missing, or uses connect native.

Common situations: Copy-pasting an expose example onto a service that only has connect {} without sidecar_service; assuming expose works with connect-native services; upgrading an HCL file where the sidecar block was removed but checks kept expose = true.

Related errors


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