netbirdio/netbird · error
target_id is required for L4 services
Error message
target_id is required for L4 services
What it means
Returned by validateL4Target when the single target of a tcp/udp/tls service has an empty TargetId. For peer/host/domain targets the ID identifies the peer or network resource to dial; for cluster targets target_id itself carries the cluster address that the proxy resolves at request time (the Host field is not used for that). Note this function also force-sets target.Enabled=true because per-target disable is meaningless when there is exactly one target - use the service-level Enabled flag.
Source
Thrown at management/internals/modules/reverseproxy/service/service.go:1032
return nil
}
if strings.ContainsAny(host, " \t/") {
return fmt.Errorf("target %d: host %q contains invalid characters", idx, host)
}
if _, _, err := net.SplitHostPort(host); err == nil {
return fmt.Errorf("target %d: host %q must not include a port (set target.port instead)", idx, host)
}
return nil
}
func (s *Service) validateL4Target(target *Target) error {
// L4 services have a single target; per-target disable is meaningless
// (use the service-level Enabled flag instead). Force it on so that
// buildPathMappings always includes the target in the proto.
target.Enabled = true
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:View on GitHub (pinned to 93e97f4bf1)
Solutions
- Set target_id to the peer ID (peer targets), resource identifier, or - for cluster targets - the cluster address string.
- Do not rely on target.host to identify the upstream for L4; it is only a dial address for subnet/direct-upstream targets.
- If the upstream has no NetBird identity at all, use target_type "host" or "domain" with direct_upstream and still provide a target_id.
Example fix
// before
{ "mode": "tcp", "listen_port": 5432,
"targets": [ { "target_type": "peer", "host": "10.10.0.5", "port": 5432 } ] }
// after
{ "mode": "tcp", "listen_port": 5432,
"targets": [ { "target_type": "peer", "target_id": "peer-a-id", "port": 5432 } ] } Defensive patterns
Strategy: validation
Validate before calling
func checkL4TargetID(t Target) error {
if t.TargetId == "" {
return errors.New("target_id is required for L4 targets")
}
return nil
} Type guard
func hasL4TargetID(t Target) bool {
return t.TargetId != ""
} Try / catch
if err := svc.Validate(); err != nil {
if strings.Contains(err.Error(), "target_id is required for L4") {
return respondBadRequest(errors.New("set target_id: peer ID, resource ID, or cluster address"))
}
return respondBadRequest(err)
} Prevention
- Resolve peer/resource IDs from the management API before building the target - never hand-type them.
- For cluster targets remember the address lives in target_id, not host.
- Make target_id a required field in your client models for L4 services.
When it happens
Trigger: Submitting an L4 target with only host and port filled (host:port style config) and no target_id; a cluster target where the address was placed in the host field instead of target_id; constructing targets programmatically and never populating the ID.
Common situations: Porting a plain host:port forward definition where there was never an ID concept. Automation that copies HTTP targets (where host is later overwritten by peer lookup) into an L4 service. UI forms that make target_id optional.
Related errors
- TCP/UDP services must have exactly one target
- domain is required for TCP/UDP services (used for cluster de
- auth is not supported for TCP/UDP services
- TLS services must have exactly one target
- target port is required for L4 services
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/81a858ced3f5f767.
Report an issue: GitHub.