netbirdio/netbird · error

skip_tls_verify is not supported for L4 services

Error message

skip_tls_verify is not supported for L4 services

What it means

Returned by validateL4Target when target.options.skip_tls_verify is true. That flag instructs the HTTP proxy's TLS client to not verify the upstream certificate when the proxy itself speaks HTTPS to the backend; in tcp/udp/tls modes the proxy never terminates or originates TLS - it relays encrypted bytes - so there is no handshake it could skip verification on. Self-signed upstream certificates are therefore irrelevant (and safe) for L4 services.

Source

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

			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
}

// Service mode constants.
const (
	ModeHTTP = "http"
	ModeTCP  = "tcp"
	ModeUDP  = "udp"
	ModeTLS  = "tls"
)

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Set skip_tls_verify to false (or omit it) on the L4 target - end-to-end TLS means clients do the verifying.
  2. If the proxy must terminate TLS and skip upstream verification, use mode http with target_protocol https.
  3. Install the CA on the connecting clients instead if the passthrough cert is private.

Example fix

// before
{ "mode": "tls", "targets": [ { "target_id": "peer-a", "port": 443,
  "options": { "skip_tls_verify": true } } ] }

// after
{ "mode": "tls", "targets": [ { "target_id": "peer-a", "port": 443 } ] }
Defensive patterns

Strategy: validation

Validate before calling

func checkL4NoSkipTLSVerify(o TargetOptions) error {
	if o.SkipTLSVerify {
		return errors.New("skip_tls_verify is http-only; remove it from L4 targets")
	}
	return nil
}

Type guard

func isL4SkipTLSVerifyClean(o TargetOptions) bool {
	return !o.SkipTLSVerify
}

Try / catch

if err := svc.Validate(); err != nil {
	if strings.Contains(err.Error(), "skip_tls_verify is not supported for L4") {
		return respondBadRequest(errors.New("passthrough TLS is end-to-end; clients verify the cert"))
	}
	return respondBadRequest(err)
}

Prevention

When it happens

Trigger: An L4 target whose options block carries skip_tls_verify: true, typically copied from an HTTPS HTTP-mode target that had a self-signed cert; flipping a service from http (https target_protocol) to tls passthrough without clearing the flag.

Common situations: 'My upstream uses a self-signed cert, better set skip_tls_verify' applied to a passthrough where the client, not the proxy, validates the cert. Template reuse across modes. Tooling that copies the whole options block verbatim.

Related errors


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