rqlite/rqlite · error
invalid SQLite data
Error message
invalid SQLite data
What it means
Store restores a node (e.g., via /boot) by receiving SQLite data, writing it to a temp file, and validating it with sql.IsValidSQLiteFile before installing it. If the uploaded bytes are not a valid SQLite database (bad header/format, truncated transfer, compressed data sent uncompressed), the restore is aborted with 'invalid SQLite data' and the node's existing database is left untouched.
Source
Thrown at store/store.go:2012
defer os.Remove(f.Name())
defer f.Close()
cw := progress.NewCountingWriter(f)
cm := progress.StartCountingMonitor(func(n int64) {
s.logger.Printf("boot process installed %d bytes (%s)", n, humanize.Bytes(uint64(n)))
}, cw)
n, err := func() (int64, error) {
defer cm.StopAndWait()
defer f.Close()
return io.Copy(cw, r)
}()
if err != nil {
return n, err
}
// Confirm the data is a valid SQLite database.
if !sql.IsValidSQLiteFile(f.Name()) {
return n, fmt.Errorf("invalid SQLite data")
}
// Raft won't snapshot unless there is at least one unsnapshotted log entry,
// so prep that now before we do anything destructive.
if af, err := s.Noop("boot"); err != nil {
return n, err
} else if err := af.Error(); err != nil {
return n, err
}
// Swap in new database file.
if err := s.db.Swap(f.Name(), s.dbConf.FKConstraints, true); err != nil {
return n, fmt.Errorf("error swapping database file: %v", err)
}
// Swapping in a new database unregisters any registered CDC hooks, so signal that it
// needs to be reregistered on the next change.
s.cdcRegistered.Unset()View on GitHub (pinned to 7586a4d1bd)
Solutions
- Upload an actual SQLite database file (from /db/backup without fmt=sql), not a SQL text dump.
- Match compression flags: add ?compress to the boot request if the payload is gzipped, or send uncompressed data without it.
- Validate the file locally before upload: `file backup.db` should say 'SQLite 3 database'.
- Re-download the backup if truncated; compare checksums with the source.
Example fix
# before curl -X POST http://node:4001/boot --data-binary @backup.sql.gz # after curl -X POST 'http://node:4001/boot?compress' --data-binary @backup.db.gz
Defensive patterns
Strategy: validation
Validate before calling
hdr := make([]byte, 16)
f, _ := os.Open(restoreFile)
f.Read(hdr)
if string(hdr[:15]) != "SQLite format 3" {
return errors.New("not a SQLite database; use /db/backup without fmt=sql")
} Type guard
func isValidSQLiteFileHeader(b []byte) bool {
return len(b) >= 16 && string(b[:15]) == "SQLite format 3"
} Try / catch
resp, err := http.Post(bootURL, "application/octet-stream", body)
if resp.StatusCode != 200 && strings.Contains(readBody(resp), "invalid SQLite data") {
// wrong format: re-export with /db/backup, recheck compress flag
} Prevention
- Restore only from /db/backup output (binary), never from fmt=sql dumps
- Keep ?compress consistent between backup and boot
- Checksum backups after download and before restore
- Test restores with `file` / sqlite3 CLI before pointing at production
When it happens
Trigger: POSTing to /boot with a body that is not a genuine SQLite file: an SQL text dump instead of a .db file, a gzip-compressed file uploaded without ?compress flag (or vice versa), truncated/corrupt download, or the wrong file entirely.
Common situations: Operators backing up with ?fmt=sql and then trying to boot from the SQL dump; curl scripts misplacing the ?compress parameter; interrupted downloads reused as restore sources; backups from non-SQLite sources.
Related errors
- file %s is not a valid SQLite file
- invalid backup format
- invalid vacuum
- %s is not a valid SQLite file
- backup database: %s
AI-assisted analysis of rqlite/rqlite@7586a4d1bd (2026-09-03).
Data as JSON: /api/errors/c6295e2a4f7731f3.
Report an issue: GitHub.