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
- Add at least one STUN server in 'host:port' form, e.g. "stun.l.google.com:19302".
- Verify every entry contains an explicit port.
- 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
- Ship a default STUN entry (e.g. stun.l.google.com:19302) with every Realm config.
- Enforce host:port form in generators.
- Remember STUN is mandatory for Realm — there is no opt-out flag.
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
- gecko: invalid min/max packet size
- invalid scheme + u.Scheme
- Config: unknown transport protocol: + p
- bridge tag is empty
- bridge domain is empty
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/481cfb279c002685.
Report an issue: GitHub.