go-sql-driver/mysql · error

unexpected resp from server for caching_sha2_password…

Error message

unexpected resp from server for caching_sha2_password, perform full authentication

What it means

Returned during caching_sha2_password full authentication over a non-TLS, non-unix connection (auth.go:426). The client requests the server's RSA public key and expects an AuthMoreData (0x01) frame; if the first byte of the response differs, the handshake is treated as broken.

Solutions

  1. Use TLS (tls=true) or a unix socket so the driver sends the password as cleartext and skips key exchange entirely.
  2. Pre-load the server's RSA public key via the DSN (serverPubKey=<name> registered with mysql.RegisterServerPubKey) to avoid requesting it dynamically.
  3. Remove any proxy that rewrites the auth handshake, or configure it to be fully transparent.
  4. Upgrade the server/proxy to a version that correctly implements caching_sha2_password full auth.

Example fix

// before: plaintext TCP, cold cache -> key-exchange desync
dsn := "user:pass@tcp(mysql8:3306)/db"

// after: TLS removes the need for the public-key exchange
dsn := "user:pass@tcp(mysql8:3306)/db?tls=true"
// or pin the key:
//   mysql.RegisterServerPubKey("mysql8", pemBytes)
//   dsn := "...?serverPubKey=mysql8"
Defensive patterns

Strategy: retry

Validate before calling

// Avoid the dynamic key exchange entirely: prefer TLS/unix or pin the key.
if !tlsAvailable && serverPubKeyPEM == "" {
    return errors.New("caching_sha2 over plaintext needs TLS or a pinned pubkey")
}

Type guard

func isCachingSha2BadResp(err error) bool {
    return err != nil && strings.Contains(err.Error(), "caching_sha2_password, perform full authentication")
}

Try / catch

if isCachingSha2BadResp(err) {
    // switch to TLS/unix, or pin serverPubKey, then retry the connection.
}

Prevention

When it happens

Trigger: First (uncached) login of a caching_sha2_password user over plaintext TCP without a configured server public key, where the server's response is not the expected AuthMoreData frame — e.g. a proxy/MITM altering the handshake, a server bug, or stream corruption during key exchange.

Common situations: MySQL 8.0 default auth (caching_sha2_password) over unencrypted networks; a load balancer or connection-pooling proxy that doesn't pass through auth-more-data; first connection after server restart (cache cold).

Understand the failure class

Related errors


AI-assisted analysis of go-sql-driver/mysql@03d76c7e07 (2026-08-07). Data as JSON: /api/errors/fd4cceb256f1494b. Report an issue: GitHub.

Appendix: source

Thrown at auth.go:426

					pubKey := mc.cfg.pubKey
					if pubKey == nil {
						// request public key from server
						data, err := mc.buf.takeSmallBuffer(4 + 1)
						if err != nil {
							return err
						}
						data[4] = cachingSha2PasswordRequestPublicKey
						err = mc.writePacket(data)
						if err != nil {
							return err
						}

						if data, err = mc.readPacket(); err != nil {
							return err
						}

						if data[0] != iAuthMoreData {
							return fmt.Errorf("unexpected resp from server for caching_sha2_password, perform full authentication")
						}

						// parse public key
						block, rest := pem.Decode(data[1:])
						if block == nil {
							return fmt.Errorf("no pem data found, data: %s", rest)
						}
						pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
						if err != nil {
							return err
						}
						pubKey = pkix.(*rsa.PublicKey)
					}

					// send encrypted password
					err = mc.sendEncryptedPassword(oldAuthData, pubKey)
					if err != nil {
						return err

View on GitHub (pinned to 03d76c7e07)