Most SQLite backup advice still says "stop the service and copy the file." VACUUM INTO (SQLite 3.27+) gives you something better: a consistent, compacted backup that you can take while the service is running.

VACUUM INTO 'backup-2026-08-26.db';

It writes a brand-new database file containing the current data, with free pages reclaimed. No -wal juggling, no lock dance, no "oops I copied before the checkpoint."

Why I trust it over a file copy

  • Consistent by construction. It reads through SQLite's own engine, so you never get a torn copy. In WAL mode this matters even more — a plain cp of app.db can miss the latest writes sitting in -wal.
  • Safe while running. SQLite handles the concurrent access internally. I run it on a schedule with zero downtime.
  • Compacts the file. The output is defragmented — often noticeably smaller than the source.

When it is not the answer

VACUUM INTO reads the database, so it fails on databases whose structure is broken. In my own corruption testing, this was the pattern:

  • Bit-rot inside row content (garbled bytes in a value): VACUUM INTO succeeds — the copy is structurally valid, though the corrupted content rides along.
  • Truncated file / zeroed page (structure gone): VACUUM INTO fails with database disk image is malformed (11).

So use it as your regular backup, not your disaster recovery. For a broken file, the fallback is a real backup or the CLI's .recover.

My routine

  1. Daily: VACUUM INTO to a timestamped file (running, zero downtime).
  2. Weekly: restore one backup into a scratch DB and run PRAGMA integrity_check — prove the backups actually work.
  3. Before any risky migration: a fresh VACUUM INTO.

If you are about to build a SQLite-backed service, estimate the on-disk size first with the calculator below — then set up this backup routine before you have data you cannot afford to lose.

Estimated database size436.2 MB
Raw data + indexes370.0 MB
Approx. pages (4 KiB)111662