XTLS/Xray-core · error

failed to add user

Error message

failed to add user

What it means

Server-startup error returned when validator.Add(u) rejects a user during NewServer. In practice the cause is [630]: mixing a non-AEAD cipher with an existing user in a single-port multi-user setup is disallowed because legacy ciphers cannot be distinguished per user on one port. It aborts the whole inbound at startup (AtError).

Source

Thrown at proxy/shadowsocks/server.go:41

type Server struct {
	config        *ServerConfig
	validator     *Validator
	policyManager policy.Manager
	cone          bool
}

// NewServer create a new Shadowsocks server.
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
	validator := new(Validator)
	for _, user := range config.Users {
		u, err := user.ToMemoryUser()
		if err != nil {
			return nil, errors.New("failed to get shadowsocks user").Base(err).AtError()
		}

		if err := validator.Add(u); err != nil {
			return nil, errors.New("failed to add user").Base(err).AtError()
		}
	}

	v := core.MustFromContext(ctx)
	s := &Server{
		config:        config,
		validator:     validator,
		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
		cone:          ctx.Value("cone").(bool),
	}

	return s, nil
}

// AddUser implements proxy.UserManager.AddUser().
func (s *Server) AddUser(ctx context.Context, u *protocol.MemoryUser) error {
	return s.validator.Add(u)
}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Make every user on the single port use an AEAD cipher (aes-*-gcm, *chacha20-poly1305), or give the legacy user its own inbound/port.
  2. Prefer migrating everyone to shadowsocks-2022, which is designed for multi-user single port.
  3. Re-run xray run -test to confirm the inbound builds.

Example fix

// before: mixed ciphers on one port
"clients": [
  { "method": "chacha20-poly1305", "password": "a" },
  { "method": "aes-256-cfb", "password": "b" } ]
// after
"clients": [
  { "method": "chacha20-poly1305", "password": "a" },
  { "method": "aes-256-gcm", "password": "b" } ]
Defensive patterns

Strategy: validation

Validate before calling

// enforce all-AEAD before adding any user to a shared port
for _, u := range config.Users {
  if !isAEAD(u.CipherType) {
    return fmt.Errorf("user %q uses non-AEAD cipher; not allowed on multi-user single port", u.Email)
  }
}

Type guard

func isAEAD(t CipherType) bool {
  switch t {
  case CipherType_AES_128_GCM, CipherType_AES_256_GCM,
    CipherType_CHACHA20_POLY1305, CipherType_XCHACHA20_POLY1305:
    return true
  }
  return false
}

Try / catch

if err := validator.Add(u); err != nil {
  return fmt.Errorf("cannot add user %q to shared port: %w", u.Email, err)
}

Prevention

When it happens

Trigger: config.Users contains at least one AEAD user followed by (or preceding) a non-AEAD (legacy stream) user; validator.Add enforces the all-AEAD rule for single-port multi-user and returns an error, which NewServer wraps here.

Common situations: Gradually migrating a shared port from legacy to AEAD ciphers user by user; panels appending an old-cipher user to an existing multi-user inbound.

Related errors


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