golang-migrate/migrate · error
to use TLS client authentication, both x-tls-cert and x-tls-
Error message
to use TLS client authentication, both x-tls-cert and x-tls-key must not be empty
What it means
This library's MySQL driver supports TLS client (mutual) authentication via the custom DSN parameters x-tls-cert and x-tls-key. Because a client certificate and its private key only make sense as a pair, the driver rejects any configuration where exactly one of them is set and the other is empty. The error is returned during URL/DSN parsing in urlToMySQLConfig before any connection is made.
Source
Thrown at database/mysql/mysql.go:37
"github.com/go-sql-driver/mysql"
"github.com/golang-migrate/migrate/v4/database"
)
var _ database.Driver = (*Mysql)(nil) // explicit compile time type check
func init() {
database.Register("mysql", &Mysql{})
}
var DefaultMigrationsTable = "schema_migrations"
var (
ErrDatabaseDirty = fmt.Errorf("database is dirty")
ErrNilConfig = fmt.Errorf("no config")
ErrNoDatabaseName = fmt.Errorf("no database name")
ErrAppendPEM = fmt.Errorf("failed to append PEM")
ErrTLSCertKeyConfig = fmt.Errorf("to use TLS client authentication, both x-tls-cert and x-tls-key must not be empty")
)
type Config struct {
MigrationsTable string
DatabaseName string
NoLock bool
StatementTimeout time.Duration
}
type Mysql struct {
// mysql RELEASE_LOCK must be called from the same conn, so
// just do everything over a single conn anyway.
conn *sql.Conn
db *sql.DB
isLocked atomic.Bool
config *Config
}View on GitHub (pinned to 01a9643f14)
Solutions
- Set both x-tls-cert and x-tls-key to the client certificate and private key file paths in the DSN
- If client auth is not needed, remove both x-tls-cert and x-tls-key from the URL entirely (keep only x-tls-ca)
- Verify the final composed DSN (log or print it, minus secrets) to confirm both params survived environment/templating expansion
Example fix
// before dsn := "mysql://user:pass@tcp(db:3306)/app?tls=custom&x-tls-ca=/ca.pem&x-tls-cert=/client-cert.pem" // after dsn := "mysql://user:pass@tcp(db:3306)/app?tls=custom&x-tls-ca=/ca.pem&x-tls-cert=/client-cert.pem&x-tls-key=/client-key.pem"
Defensive patterns
Strategy: validation
Validate before calling
u, _ := url.Parse(dsn)
q := u.Query()
ccert, ckey := q.Get("x-tls-cert"), q.Get("x-tls-key")
if (ccert == "") != (ckey == "") {
return errors.New("x-tls-cert and x-tls-key must both be set or both omitted")
} Try / catch
drv, err := mysql.Open(dsn)
if errors.Is(err, mysql.ErrTLSCertKeyConfig) {
return fmt.Errorf("TLS client auth misconfigured: set both x-tls-cert and x-tls-key: %w", err)
} Prevention
- Always configure the cert/key pair together from a single config struct so one cannot be set without the other
- Validate the composed DSN in tests before deploying
- If client auth is optional, omit both params rather than leaving one blank
When it happens
Trigger: Calling mysql.WithInstance/Open with a mysql:// URL that includes a custom tls=<name> param (non-bool, not skip-verify) plus an x-tls-ca, and setting exactly one of x-tls-cert or x-tls-key while the other is empty or missing (database/mysql/mysql.go:168-171).
Common situations: Developers paste a CA-based TLS URL and add only the certificate path, forgetting the key; environment-variable interpolation silently drops one of the two params; config templating leaves one field blank; splitting the pair across config files where one entry was renamed.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- failed to append PEM
- could not parse x-no-lock as bool: %w
- could not parse x-statement-timeout as float: %w
- failed to append PEM
- no config
AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02).
Data as JSON: /api/errors/c989809495d53ff3.
Report an issue: GitHub.