XTLS/Xray-core · error

empty stunServers

Error message

empty stunServers

What it means

Thrown by Realm.Build() when the 'stunServers' array on the Realm transport config is empty. The Realm transport depends on STUN for NAT discovery, so at least one server is mandatory even though the URL itself parsed fine. Each entry must then also pass net.SplitHostPort (host:port form).

Source

Thrown at infra/conf/transport_finalmask.go:873

	token, err = url.PathUnescape(u.User.String())
	if err != nil {
		return nil, err
	}
	if token == "" {
		return nil, errors.New("invalid token", token)
	}

	id, err = url.PathUnescape(strings.TrimPrefix(u.EscapedPath(), "/"))
	if err != nil {
		return nil, err
	}
	if id == "" {
		return nil, errors.New("invalid id", id)
	}

	if len(c.StunServers) == 0 {
		return nil, errors.New("empty stunServers")
	}

	for _, s := range c.StunServers {
		_, _, err = net.SplitHostPort(s)
		if err != nil {
			return nil, err
		}
	}

	stunServers = c.StunServers

	if c.TlsConfig != nil {
		tc, err := c.TlsConfig.Build()
		if err != nil {
			return nil, err
		}
		tlsConfig = tc.(*tls.Config)
	}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Add at least one STUN server in 'host:port' form, e.g. "stun.l.google.com:19302".
  2. Verify every entry contains an explicit port.
  3. If behind no NAT, still supply a reachable public STUN server — there is no opt-out.

Example fix

// before
"realm": { "url": "realm://t@h:443/id", "stunServers": [] }
// after
"realm": { "url": "realm://t@h:443/id", "stunServers": ["stun.l.google.com:19302"] }
Defensive patterns

Strategy: validation

Validate before calling

if len(stunServers) == 0 {
    return fmt.Errorf("realm transport requires at least one stun server")
}
for _, s := range stunServers {
    if _, _, err := net.SplitHostPort(s); err != nil {
        return fmt.Errorf("stun server %q must be host:port", s)
    }
}

Prevention

When it happens

Trigger: Declaring a Realm transport with a valid url but omitting stunServers, or providing stunServers: [] in JSON. Entries like "stun.l.google.com" without a port fail the subsequent SplitHostPort check with a wrapped error.

Common situations: Copy-pasting an example that omits STUN; assuming STUN is optional; providing hostnames without ports.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/481cfb279c002685. Report an issue: GitHub.