netbirdio/netbird · error
target port is required for L4 services
Error message
target port is required for L4 services
What it means
Returned by validateL4Target when the L4 target's Port is 0 (the uint16 zero value, i.e. omitted). The proxy's path mapping builds the upstream dial address with net.JoinHostPort(target.Host, target.Port), so a zero port would literally send ":0" to the dialer; the validation closes that gap. Cluster targets are included: they resolve host:port from these same fields.
Source
Thrown at management/internals/modules/reverseproxy/service/service.go:1039
}
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:
// 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")View on GitHub (pinned to 93e97f4bf1)
Solutions
- Set target.port to the upstream's real port (1-65535).
- Remember listen_port is the proxy-side listener; target.port is what the upstream listens on - both are needed for tcp/udp.
- If your client marshals port from a string, validate and convert before sending so the field is actually present.
Example fix
// before
{ "mode": "tcp", "listen_port": 5432,
"targets": [ { "target_type": "peer", "target_id": "peer-a" } ] }
// after
{ "mode": "tcp", "listen_port": 5432,
"targets": [ { "target_type": "peer", "target_id": "peer-a", "port": 5432 } ] } Defensive patterns
Strategy: validation
Validate before calling
func checkL4TargetPort(t Target) error {
if t.Port == 0 {
return errors.New("target port must be 1-65535 for L4 targets")
}
return nil
} Type guard
func hasL4TargetPort(t Target) bool {
return t.Port != 0
} Try / catch
if err := svc.Validate(); err != nil {
if strings.Contains(err.Error(), "target port is required for L4") {
return respondBadRequest(errors.New("set the upstream port explicitly - there is no default"))
}
return respondBadRequest(err)
} Prevention
- Never rely on an implied default upstream port; always resolve and set it.
- uint16 zero means omitted server-side - keep port fields non-zero in client structs.
- Verify serialization: a string-typed port in the client must actually reach the JSON as a number.
When it happens
Trigger: An L4 target JSON with no port key (defaults to 0); relying on a 'default port' convention that this API does not have; port field typed as string in the client and silently dropped during serialization to uint16.
Common situations: Assuming listen_port on the service implies the upstream port. Copying HTTP target configs where the port lived elsewhere. Client structs where port is an int and 0 means 'unset' - the server cannot distinguish that from an invalid value, so it must be explicit.
Related errors
- TCP/UDP services must have exactly one target
- TLS services must have exactly one target
- target_id is required for L4 services
- target host is required for subnet targets
- path is not supported for L4 services
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/806d02dc7fe4c1de.
Report an issue: GitHub.