XTLS/Xray-core · error

invalid "privateKey":

Error message

invalid "privateKey": 

What it means

Thrown by the REALITY builder when privateKey fails base64 RawURL decoding or decodes to a length != 32 bytes. REALITY private keys are 32-byte x25519 scalars encoded in unpadded base64url; anything else (standard base64 with padding, hex, a pasted public key, or trailing whitespace) is rejected, and the offending value is echoed in the message.

Source

Thrown at infra/conf/transport_security.go:101

				if _, _, err = net.SplitHostPort(s); err == nil {
					c.Type = "tcp"
				}
			}
		}
		if c.Type == "" {
			return nil, errors.New(`please fill in a valid value for "target"`)
		}
		if c.Xver > 2 {
			return nil, errors.New(`invalid PROXY protocol version, "xver" only accepts 0, 1, 2`)
		}
		if len(c.ServerNames) == 0 {
			return nil, errors.New(`empty "serverNames"`)
		}
		if c.PrivateKey == "" {
			return nil, errors.New(`empty "privateKey"`)
		}
		if config.PrivateKey, err = base64.RawURLEncoding.DecodeString(c.PrivateKey); err != nil || len(config.PrivateKey) != 32 {
			return nil, errors.New(`invalid "privateKey": `, c.PrivateKey)
		}
		if c.MinClientVer != "" {
			config.MinClientVer = make([]byte, 3)
			var u uint64
			for i, s := range strings.Split(c.MinClientVer, ".") {
				if i == 3 {
					return nil, errors.New(`invalid "minClientVer": `, c.MinClientVer)
				}
				if u, err = strconv.ParseUint(s, 10, 8); err != nil {
					return nil, errors.New(`"minClientVer[`, i, `]" should be less than 256`)
				} else {
					config.MinClientVer[i] = byte(u)
				}
			}
			errors.LogWarning(context.Background(), `REALITY: Changing "minClientVer" will increase the likelihood of your server's IP being blocked by the GFW`)
		} else {
			config.MinClientVer = []byte{26, 3, 27} // change it at your own risk: https://github.com/XTLS/Xray-core/commit/af7eb68028732a8ee3c0e5d6ab2b8a657bb2e770
			errors.LogWarning(context.Background(), `REALITY: The default minimal client version is Xray-core v26.3.27, other clients may be refused to connect`)

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Regenerate with 'xray x25519' and copy the Private key line verbatim (43 chars, no padding, no whitespace).
  2. Ensure the server uses privateKey and clients use publicKey.
  3. Paste with a terminal/editor that does not add line breaks.

Example fix

// before
"privateKey": "MIIBvQIBADAN...=="  // padded / wrong key
// after
"privateKey": "Ux8Fq2m1nQ0jK3pR7sT9vW2xY4zA6bC8dE0fG1hI"  // from `xray x25519`
Defensive patterns

Strategy: validation

Validate before calling

func validX25519Key(s string) bool {
    s = strings.TrimSpace(s)
    raw, err := base64.RawURLEncoding.DecodeString(s)
    return err == nil && len(raw) == 32
}

Prevention

When it happens

Trigger: Putting the PUBLIC key into privateKey, using base64 with '=' padding, including a newline/space, or hand-editing the key string.

Common situations: Swapping the two lines output by 'xray x25519'; copying keys through a terminal that wraps/truncates lines.

Related errors


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