netbirdio/netbird · error

target host is required for subnet targets

Error message

target host is required for subnet targets

What it means

Returned by validateL4Target when target_type is "subnet" and target.host is empty. Subnet targets reach an address inside a routed network resource, and that address is carried in the host field (plus port); unlike peer targets, nothing overwrites it later, so it must be supplied up front.

Source

Thrown at management/internals/modules/reverseproxy/service/service.go:1048

	if target.TargetId == "" {
		return errors.New("target_id is required for L4 services")
	}
	// Cluster targets resolve their upstream host:port from the target's
	// own Host/Port fields just like the other L4 types — buildPathMappings
	// emits net.JoinHostPort(target.Host, target.Port) for every L4
	// target, so allowing port=0 here would let ":0" reach the proxy.
	if target.Port == 0 {
		return errors.New("target port is required for L4 services")
	}
	switch target.TargetType {
	case TargetTypePeer, TargetTypeHost, TargetTypeDomain:
		if err := validateDirectUpstreamHost(0, target); err != nil {
			return err
		}
	case TargetTypeSubnet:
		if target.Host == "" {
			return errors.New("target host is required for subnet targets")
		}
	case TargetTypeCluster:
		// target_id carries the cluster address; the proxy resolves
		// the upstream at request time.
	default:
		return fmt.Errorf("invalid target_type %q for L4 service", target.TargetType)
	}
	if target.Path != nil && *target.Path != "" && *target.Path != "/" {
		return errors.New("path is not supported for L4 services")
	}
	if target.Options.SessionIdleTimeout < 0 {
		return errors.New("session_idle_timeout must be positive for L4 services")
	}
	if target.Options.RequestTimeout < 0 {
		return errors.New("request_timeout must be positive for L4 services")
	}
	if target.Options.SkipTLSVerify {
		return errors.New("skip_tls_verify is not supported for L4 services")

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Set host to the IP (or resolvable name) inside the subnet resource, e.g. "10.20.1.10".
  2. Keep port set as well - the next check requires it.
  3. If your destination is actually a peer, use target_type "peer" with its target_id instead.

Example fix

// before
{ "target_type": "subnet", "target_id": "subnet-res-id", "port": 5432 }

// after
{ "target_type": "subnet", "target_id": "subnet-res-id", "host": "10.20.1.10", "port": 5432 }
Defensive patterns

Strategy: validation

Validate before calling

func checkSubnetHost(t Target) error {
	if t.TargetType == "subnet" && t.Host == "" {
		return errors.New("host (address inside the subnet) is required for subnet targets")
	}
	return nil
}

Type guard

func hasSubnetHost(t Target) bool {
	return t.TargetType != "subnet" || t.Host != ""
}

Try / catch

if err := svc.Validate(); err != nil {
	if strings.Contains(err.Error(), "target host is required for subnet") {
		return respondBadRequest(errors.New("set host to the destination address inside the subnet resource"))
	}
	return respondBadRequest(err)
}

Prevention

When it happens

Trigger: An L4 target declared as target_type=subnet with only target_id set (e.g. the subnet resource ID) and no host; templates that leave host empty expecting the proxy to derive it.

Common situations: Assuming target_id alone identifies the destination for subnet resources the way it does for peers. Splitting configs where the resource ID was populated by automation but the address field was forgotten.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/92cadef97898393b. Report an issue: GitHub.