vitessio/vitess · error

could not back up existing

Error message

could not back up existing %v: %v

What it means

RefreshConfig detected a config change and tried to back up the current my.cnf by renaming it to my.cnf.previous, but os.Rename failed. Wrapped from the OS; usually a permissions or cross-filesystem issue.

Solutions

  1. Check that cnf.Path still exists and the containing directory is writable.
  2. Fix ownership of my.cnf and the directory (chown to the vitess user).
  3. Remove a stale my.cnf.previous if it is read-only and blocking the rename.
  4. Re-run RefreshConfig after resolving permissions.

Example fix

// before
-r--r--r-- my.cnf (owned by root), dir not writable
// after
chown vitess:vitess my.cnf my.cnf.previous 2>/dev/null; chmod u+w . my.cnf
Defensive patterns

Strategy: validation

Validate before calling

dir := path.Dir(cnf.Path)
if info, err := os.Stat(dir); err != nil || !info.IsDir() {
    return fmt.Errorf("dir %s not usable for rename: %w", dir, err)
}
if unix.Access(dir, unix.W_OK) != nil {
    return fmt.Errorf("dir %s not writable", dir)
}

Try / catch

if err := mysqld.RefreshConfig(ctx, cnf); err != nil {
    if strings.Contains(err.Error(), "could not back up existing") {
        log.Errorf("fix ownership/permissions on %s before refreshing: %v", cnf.Path, err)
    }
    return err
}

Prevention

When it happens

Trigger: Mysqld.RefreshConfig where bytes differ: os.Rename(cnf.Path, cnf.Path+".previous") fails because cnf.Path doesn't exist, is owned by another user, or the directory is not writable.

Common situations: my.cnf deleted between the read and rename (TOCTOU); running refresh as different user than tablet init; directory mounted read-only.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/6bf793c153de06cb. Report an issue: GitHub.

Appendix: source

Thrown at go/vt/mysqlctl/mysqld.go:1596

	existing, err := os.ReadFile(cnf.Path)
	if err != nil {
		return fmt.Errorf("could not read existing file %v: %v", cnf.Path, err)
	}
	updated, err := os.ReadFile(f.Name())
	if err != nil {
		return fmt.Errorf("could not read updated file %v: %v", f.Name(), err)
	}

	if bytes.Equal(existing, updated) {
		log.Info("No changes to my.cnf. Continuing.")
		return nil
	}

	backupPath := cnf.Path + ".previous"
	err = os.Rename(cnf.Path, backupPath)
	if err != nil {
		return fmt.Errorf("could not back up existing %v: %v", cnf.Path, err)
	}
	err = os.Rename(f.Name(), cnf.Path)
	if err != nil {
		return fmt.Errorf("could not move %v to %v: %v", f.Name(), cnf.Path, err)
	}
	log.Info(fmt.Sprintf("Updated my.cnf. Backup of previous version available in %v", backupPath))

	return nil
}

// ReinitConfig updates the config file as if Mysqld is initializing. At the
// moment it only randomizes ServerID because it's not safe to restore a replica
// from a backup and then give it the same ServerID as before, MySQL can then
// skip transactions in the replication stream with the same server_id.
func (mysqld *Mysqld) ReinitConfig(ctx context.Context, cnf *Mycnf) error {
	log.Info("Mysqld.ReinitConfig")

	// Execute as remote action on mysqlctld if requested.

View on GitHub (pinned to 01a25a7d17)