MHSanaei/3x-ui · error

wireguard publicKey: %w

Error message

wireguard publicKey: %w

What it means

Wireguard branch of the Xray account builder: the required 'publicKey' passed the presence check but wgutil.KeyToHex rejected it. KeyToHex base64-decodes the value and expects exactly the 32-byte Wireguard key length, so this wraps either a base64 decode error or a wrong-length error — the publicKey is not a valid Wireguard public key.

Source

Thrown at internal/xray/api.go:630

			CipherType: ssCipherType,
		}), nil
	case "hysteria":
		auth, err := getRequiredUserString(user, "auth")
		if err != nil {
			return nil, err
		}

		return serial.ToTypedMessage(&hysteriaAccount.Account{
			Auth: auth,
		}), nil
	case "wireguard":
		pubB64, err := getRequiredUserString(user, "publicKey")
		if err != nil {
			return nil, err
		}
		pubHex, err := wgutil.KeyToHex(pubB64)
		if err != nil {
			return nil, fmt.Errorf("wireguard publicKey: %w", err)
		}

		pskB64, err := getOptionalUserString(user, "preSharedKey")
		if err != nil {
			return nil, err
		}
		pskHex, err := wgutil.KeyToHex(pskB64)
		if err != nil {
			return nil, fmt.Errorf("wireguard preSharedKey: %w", err)
		}

		allowed := collectStringSlice(user["allowedIPs"])
		if len(allowed) == 0 {
			return nil, common.NewError("wireguard: allowedIPs required")
		}

		keepAlive, err := getOptionalUserString(user, "keepAlive")
		if err != nil {

View on GitHub (pinned to ad32144c42)

Solutions

  1. Regenerate the keypair and paste the base64 (base64nopad) form of the PUBLIC key — 'wg pubkey' output or 'wg genkey | wg pubkey' is the canonical source; Xray expects the same 32-byte base64 form as wireguard-go.
  2. Verify length: base64 of 32 bytes is 43 chars without padding (44 with '='). A 64-char hex string means you copied hex — convert it (echo <hex> | xxd -r -p | base64) or re-derive from the tool.
  3. Ensure no leading/trailing whitespace or quotes snuck into the stored value.

Example fix

// before — hex form pasted
user["publicKey"] = "8f4a1c..." // 64 hex chars

// after — base64 (43-44 chars) form
user["publicKey"] = "j0ocQKx+Zr1f3..." // base64 of the same 32 bytes
Defensive patterns

Strategy: validation

Validate before calling

// Validate a Wireguard key (base64, 32 bytes) before it reaches Xray
func isValidWGKeyB64(s string) bool {
    s = strings.TrimSpace(s)
    if len(s) == 44 && strings.HasSuffix(s, "=") {
        s = s[:43]
    }
    if len(s) != 43 {
        return false
    }
    raw, err := base64.RawStdEncoding.DecodeString(s)
    return err == nil && len(raw) == 32
}

Type guard

null

Try / catch

if err != nil && strings.Contains(err.Error(), "wireguard publicKey") {
    // regenerate via `wg genkey | wg pubkey`, paste base64 form; no retry
}

Prevention

When it happens

Trigger: Providing a wireguard client publicKey that is: hex instead of base64 (Wireguard tools often print hex), a base64 string of the wrong length, a private key pasted by mistake, or a key with whitespace/typo corruption.

Common situations: Copy-paste from 'wg show' output (hex form) into a field expecting base64; mixing up private/public keys when creating a peer; truncated keys from terminal copy; keys generated by a non-standard tool.

Related errors


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/3b68f7418befcf15. Report an issue: GitHub.