“Database disk image is malformed” after restoring a backup is usually not corruption.
The error names the file, not the cause. In practice it is raised when a page the file claims to have cannot be read, or when the first 100 bytes are not a SQLite header at all — both of which happen to “good” backups, because a backup taken the wrong way is a file with a correct header and nothing in it.
Three different failures, one message
1. The file is a stub (most common)
An app in journal_mode=WAL keeps recent commits in app.db-wal and only
periodically moves them into app.db. A copy, a tar, a snapshot, or a “backup” volume
that grabs only app.db captures the size the file had at the last checkpoint — often
one page. Restoring it produces a 4 KB file that opens without error and contains no tables. The
moment you then write to it, or run a repair tool against it, you get the malformed error: the
tool was told to expect tables that were never in that file.
2. The file is truncated or was overwritten in place
Header page_count says 13 pages, the file is 5 pages long: the tail is simply
missing. This is the one case where the three page counts disagree, and it is the only one
integrity_check will also catch.
3. The file is fine and the error comes from the reader
A 32-bit build, a filesystem that returns short reads, or a second process holding the
-wal can all produce the same message. Copying the pair to a different machine and
checking the bytes first is how you tell this apart from 1 and 2.
What actually helps
- Stop writing to the directory. Every open of a WAL database rewrites the side files.
- Collect .db + -wal + -shm together, then check the copy, not the original.
- Do not run a “repair” before you know which of the three cases you are in: repairs rewrite the header, and a stub file that gets “repaired” becomes a file nobody can tell from a real one.
- If the data is only in the
-wal, the working command issqlite3 app.db ".backup fixed.db"with the-walstill next to it — that folds the log into a new single file without touching the original.
How to check before you choose
The check below reads the header, the file length, and any -wal you attach, and
says which of the three cases you are in. It takes a few hundred milliseconds and it does not
open the file with sqlite3, so it cannot checkpoint or delete your log.
Check one .db file (max 20 MB; add the -wal if you have it)
Read-only byte inspection: page counts, WAL commit, freelist residue. The file is deleted before the answer is written, and nothing about its content is logged.