go-sql-driver/mysql · error

key '%s' is reserved

Error message

key '%s' is reserved

What it means

Returned by mysql.RegisterTLSConfig (utils.go:57-59) when the requested key collides with a reserved value the DSN parser treats specially. The parser interprets tls=true/false, tls=skip-verify, and tls=preferred as built-in modes, so registering a config under any of those names would be ambiguous and is rejected.

Source

Thrown at utils.go:59

//	    log.Fatal(err)
//	}
//	if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
//	    log.Fatal("Failed to append PEM.")
//	}
//	clientCert := make([]tls.Certificate, 0, 1)
//	certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
//	if err != nil {
//	    log.Fatal(err)
//	}
//	clientCert = append(clientCert, certs)
//	mysql.RegisterTLSConfig("custom", &tls.Config{
//	    RootCAs: rootCertPool,
//	    Certificates: clientCert,
//	})
//	db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
func RegisterTLSConfig(key string, config *tls.Config) error {
	if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" || strings.ToLower(key) == "preferred" {
		return fmt.Errorf("key '%s' is reserved", key)
	}

	tlsConfigLock.Lock()
	if tlsConfigRegistry == nil {
		tlsConfigRegistry = make(map[string]*tls.Config)
	}

	tlsConfigRegistry[key] = config
	tlsConfigLock.Unlock()
	return nil
}

// DeregisterTLSConfig removes the tls.Config associated with key.
func DeregisterTLSConfig(key string) {
	tlsConfigLock.Lock()
	if tlsConfigRegistry != nil {
		delete(tlsConfigRegistry, key)
	}

View on GitHub (pinned to c426bd9379)

Solutions

  1. Choose a non-reserved, descriptive key such as "custom", "client-cert", or your app name.
  2. Reference the same key in the DSN: `?tls=custom`.
  3. If you only need skip-verify/preferred behavior, do not call RegisterTLSConfig at all — just use the keyword in the DSN.

Example fix

// before
mysql.RegisterTLSConfig("true", cfg)
// after
mysql.RegisterTLSConfig("custom", cfg)
// dsn: ?tls=custom
Defensive patterns

Strategy: validation

Validate before calling

reserved := map[string]bool{"true":true,"false":true,"1":true,"0":true,"TRUE":true,"FALSE":true,"True":true,"False":true,"skip-verify":true,"preferred":true}
if reserved[strings.ToLower(key)] { return errors.New("tls key is reserved") }

Type guard

func isReservedTLSKey(key string) bool { _, ok := readBoolExported(key); ok = ok || strings.EqualFold(key,"skip-verify") || strings.EqualFold(key,"preferred"); return ok }

Try / catch

if err := mysql.RegisterTLSConfig(key, cfg); err != nil { key = "custom"; mysql.RegisterTLSConfig(key, cfg) }

Prevention

When it happens

Trigger: Calling mysql.RegisterTLSConfig with key equal to one of: "1","true","TRUE","True","0","false","FALSE","False" (the readBool set, utils.go:58), or "skip-verify"/"preferred" (case-insensitive). The check at utils.go:58 returns the reserved-key error.

Common situations: Naming a TLS config "true" or "skip-verify" while also using the built-in meaning in a DSN; refactoring that renames a config to a keyword; confusion between the boolean shorthand and a custom name.

Related errors


AI-assisted analysis of go-sql-driver/mysql@c426bd9379 (2026-08-04). Data as JSON: /data/errors/9e0bf3227af15961.json. Report an issue: GitHub.