netbirdio/netbird · error
path is not supported for L4 services
Error message
path is not supported for L4 services
What it means
Returned by validateL4Target when the target's Path pointer is non-nil and set to anything other than "" or "/". Path-based routing is an HTTP concept (choosing a backend by URL path); an L4 service forwards opaque bytes and never inspects them, so a path cannot influence routing. The empty-string and "/" exceptions exist so HTTP-derived defaults survive the copy.
Source
Thrown at management/internals/modules/reverseproxy/service/service.go:1057
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")
}
if target.Options.PathRewrite != "" {
return errors.New("path_rewrite is not supported for L4 services")
}
if len(target.Options.CustomHeaders) > 0 {
return errors.New("custom_headers is not supported for L4 services")
}
return nil
}View on GitHub (pinned to 93e97f4bf1)
Solutions
- Remove the path key (or set it to null, "", or exactly "/") on L4 targets.
- Clean shared templates so mode-specific fields are conditionally emitted.
- If you were trying to route by path, that is mode http territory - restructure the service accordingly.
Example fix
// before
{ "target_type": "peer", "target_id": "peer-a", "port": 5432, "path": "/db" }
// after
{ "target_type": "peer", "target_id": "peer-a", "port": 5432 } Defensive patterns
Strategy: validation
Validate before calling
func checkL4NoPath(t Target) error {
if t.Path != nil && *t.Path != "" && *t.Path != "/" {
return errors.New("path is http-only; remove it from L4 targets")
}
return nil
} Type guard
func isL4PathClean(t Target) bool {
return t.Path == nil || *t.Path == "" || *t.Path == "/"
} Try / catch
if err := svc.Validate(); err != nil {
if strings.Contains(err.Error(), "path is not supported for L4") {
return respondBadRequest(errors.New("delete the path key from L4 targets"))
}
return respondBadRequest(err)
} Prevention
- Strip HTTP-only keys (path, path_rewrite, custom_headers, skip_tls_verify) when generating L4 payloads.
- Avoid YAML anchors shared across HTTP and L4 service templates.
- Diff payloads against a known-good golden file per mode in CI.
When it happens
Trigger: An L4 target whose JSON carries "path": "/api" left over from an HTTP multi-target definition; YAML anchors shared between HTTP and TCP service templates that include a path key.
Common situations: Cloning an HTTP service (where each target had a path) into a tcp/udp/tls service. Tooling that emits a default path of "/v1" for every target regardless of mode.
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 port is required for L4 services
- target host is required for subnet targets
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/922a2fa51a39cb4d.
Report an issue: GitHub.