XTLS/Xray-core · error

failed to get server spec

Error message

failed to get server spec

What it means

Returned by NewClient for the trojan outbound when protocol.NewServerSpecFromPB fails to convert the protobuf server definition into a runtime ServerSpec. It wraps the underlying error; typical base causes are an invalid destination (bad address/port) or an unparseable account entry in the config.

Source

Thrown at proxy/trojan/client.go:36

	"github.com/xtls/xray-core/transport"
	"github.com/xtls/xray-core/transport/internet"
	"github.com/xtls/xray-core/transport/internet/stat"
)

// Client is a inbound handler for trojan protocol
type Client struct {
	server        *protocol.ServerSpec
	policyManager policy.Manager
}

// NewClient create a new trojan client.
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
	if config.Server == nil {
		return nil, errors.New(`no target server found`)
	}
	server, err := protocol.NewServerSpecFromPB(config.Server)
	if err != nil {
		return nil, errors.New("failed to get server spec").Base(err)
	}

	v := core.MustFromContext(ctx)
	client := &Client{
		server:        server,
		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
	}
	return client, nil
}

// Process implements OutboundHandler.Process().
func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
	outbounds := session.OutboundsFromContext(ctx)
	ob := outbounds[len(outbounds)-1]
	if !ob.Target.IsValid() {
		return errors.New("target not specified")
	}
	ob.Name = "trojan"

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Read the wrapped Base error — it pinpoints which field (address/port/account) failed.
  2. Fix address to a valid IP/hostname and port to 1-65535.
  3. Check the trojan settings schema in the current version and align field names (servers[].address/port/password).
  4. Run xray run -test against the config to catch it before runtime.

Example fix

// before
{ "address": "s.example.com:443", "port": 0 }

// after
{ "address": "s.example.com", "port": 443 }
Defensive patterns

Strategy: validation

Validate before calling

func ValidateServerEntry(addr string, port uint32) error {
	if addr == "" { return errors.New("address required") }
	if port == 0 || port > 65535 { return errors.New("port out of range") }
	if net.ParseIP(addr) == nil {
		if _, err := idna.ToASCII(addr); err != nil { return err }
	}
	return nil
}

Try / catch

if _, err := trojan.NewClient(ctx, cfg); err != nil {
	if strings.Contains(err.Error(), "failed to get server spec") {
		// surface the wrapped base error to the config author
	}
}

Prevention

When it happens

Trigger: The trojan outbound server entry exists but is malformed: empty address, port 0 or > 65535, wrong account type, or address that cannot be parsed into a net destination.

Common situations: Typo'd domain containing illegal characters; port field missing in JSON; users/password array shaped incorrectly; legacy config using outdated field names after a core upgrade.

Related errors


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