hashicorp/nomad · error

service %q cannot have "connect" block, only services define

Error message

service %q cannot have "connect" block, only services defined in a "group" block can

What it means

Connect (Consul service mesh) integration is only supported for services declared at the group level, because Connect sidecar proxies and networking are managed per allocation, not per task. A task-level service carrying a `connect` block is rejected during job validation.

Source

Thrown at nomad/structs/structs.go:8515

		}
		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 {
			mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q cannot have \"connect\" block, only services defined in a \"group\" block can", service.Name))
		}

		// Ensure that check names are unique and have valid ports
		knownChecks := make(map[string]struct{})
		for _, check := range service.Checks {
			if _, ok := knownChecks[check.Name]; ok {
				mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q is duplicate", check.Name))
			}
			knownChecks[check.Name] = struct{}{}

			if check.AddressMode == AddressModeAlloc {
				mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q cannot use address_mode=\"alloc\", only checks defined in a \"group\" service block can use this mode", service.Name))
			}

			if check.AddressMode == AddressModeAllocIPv6 {
				mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q cannot use address_mode=\"alloc_ipv6\", only checks defined in a \"group\" service block can use this mode", service.Name))
			}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Move the service (with its connect block) up to the `group` level.
  2. Remove the connect block if mesh is not needed for the task-level service.
  3. Re-run `nomad job validate` after restructuring to confirm compliance.

Example fix

// before
task "api" {
  service {
    name = "api"
    connect { sidecar_service {} }
  }
}

// after
group "api" {
  network { mode = "cni/bridge" }
  service {
    name = "api"
    connect { sidecar_service {} }
  }
  task "api" { }
}
Defensive patterns

Strategy: validation

Validate before calling

for _, tg := range job.TaskGroups {
  for _, t := range tg.Tasks {
    for _, s := range t.Services {
      if s.Connect != nil {
        return fmt.Errorf("service %q in task %s: connect block must be at group level", s.Name, t.Name)
      }
    }
  }
}

Prevention

When it happens

Trigger: Submitting a job where a service nested inside a `task` block contains a `connect { sidecar_service {} }` (or any connect) stanza.

Common situations: Following Consul-connect examples that place service at group level but implementing them inside a task; migrating a task-scoped service to service mesh; copy-paste from group-level examples.

Related errors


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