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

  1. Set both x-tls-cert and x-tls-key to the client certificate and private key file paths in the DSN
  2. If client auth is not needed, remove both x-tls-cert and x-tls-key from the URL entirely (keep only x-tls-ca)
  3. 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

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

Related errors


AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02). Data as JSON: /api/errors/c989809495d53ff3. Report an issue: GitHub.