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
- Choose a non-reserved, descriptive key such as "custom", "client-cert", or your app name.
- Reference the same key in the DSN: `?tls=custom`.
- 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
- Use descriptive, non-keyword TLS config names.
- Register configs once at startup.
- Reference the exact same name in the DSN.
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
- invalid value / unknown config name: {cfg.TLSConfig}
- invalid timeTruncate value: %v, error: %w
- invalid value for server pub key name: %v
- invalid value for TLS config name: %v
- logger is nil
AI-assisted analysis of go-sql-driver/mysql@c426bd9379 (2026-08-04).
Data as JSON: /data/errors/9e0bf3227af15961.json.
Report an issue: GitHub.