go-sql-driver/mysql · warning

key ' ' is reserved

Error message

key '%s' is reserved

What it means

RegisterTLSConfig rejects key names that collide with the DSN 'tls=' shorthand: bool-parseable values (true/false/1/0) and the literals 'skip-verify' and 'preferred' (case-insensitive). These names are reserved so the DSN parser can distinguish them from custom configs.

Solutions

  1. Pick a non-reserved name for your TLS config, e.g. 'custom', 'prod-tls', or 'rds'.
  2. If you wanted the reserved behavior, use the literal directly in the DSN (?tls=true / skip-verify / preferred) instead of registering a config.
  3. Update the DSN's tls= parameter to match the new non-reserved name.

Example fix

// before
mysql.RegisterTLSConfig("skip-verify", tlsCfg) // -> reserved
// after
mysql.RegisterTLSConfig("custom", tlsCfg)
sql.Open("mysql", "user@tcp(host:3306)/db?tls=custom")
Defensive patterns

Strategy: validation

Validate before calling

// Reject reserved TLS config names before registering.
func notReservedName(name string) bool {
    if _, isBool := readBool(name); isBool { return false }
    switch strings.ToLower(name) {
    case "skip-verify", "preferred": return false
    }
    return true
}

Type guard

null

Try / catch

// RegisterTLSConfig returns the error directly.
if err := mysql.RegisterTLSConfig(name, tlsCfg); err != nil {
    if strings.Contains(err.Error(), "is reserved") {
        name = "custom" // pick a non-reserved name
    }
}

Prevention

When it happens

Trigger: Calling mysql.RegisterTLSConfig("skip-verify", cfg), RegisterTLSConfig("preferred", cfg), or RegisterTLSConfig("true"/"false"/"1"/"0", cfg).

Common situations: Choosing a config name that happens to be one of the reserved literals; auto-generating a name that lands on a reserved value.

Related errors


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

Appendix: 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 03d76c7e07)