How to recover deleted browsing history
2026-06-21 · 4 min
"Clear browsing history" deletes rows from a SQLite table. It does not scrub the disk, and it rarely touches the half-dozen other artifacts that independently record where a browser has been. Recovery works in three layers, from cheapest to deepest. Run them in order, then corroborate anything you recover against artifacts the user almost certainly forgot existed.
Layer 1: the WAL sidecar
The -wal file holds committed transactions that SQLite has not yet
checkpointed into the main .db. In practice that is the most recent
activity — frequently the exact visits an investigation cares about, and
frequently missing from the main file entirely.
- Always capture
History-walandHistory-shm(Firefox:places.sqlite-wal/-shm) alongside the main database, with matching base names. - Load the database together with its
-waland the tool applies the WAL automatically — the recent rows appear in the merged view. - Never let a tool open the database read-write before you copy. A read-write open triggers a checkpoint and rewrites the WAL out from under you. See the WAL recovery deep-dive.
If the user cleared history during the session that produced the WAL, the deletions may themselves sit in WAL frames — and older frames can still hold the pre-deletion rows.
Layer 2: freelist and unallocated pages
Deleting a row marks its cell free; it does not zero the bytes. The data lingers in the page freelist and in unallocated cell space until SQLite reuses that space. Until something overwrites it, the deleted history row is recoverable verbatim.
- This is page-level carving, not a normal query — the rows are not in any table the SQL engine will return.
- Use a dedicated SQLite-carving tool (raw page walkers, Sanderson's
walitean, Sleuth Kit's SQLite plugins) to dump freelist and unallocated content and reconstruct rows. - Be honest about the limit: a busy database overwrites freed pages quickly. The longer between deletion and acquisition, the less survives. See SQLite browser forensics for the page structure this relies on.
Layer 3: corroborate from other artifacts
When the history database is truly wiped — or you simply need a second source — several artifacts independently record visited domains. None of them is cleared by "Clear browsing history" by default, and they rarely get wiped together.
- Top Sites — a separate database of the user's most-visited URLs.
- Favicons — the page-to-icon mapping ties a favicon to the page URL
that requested it, so domains survive here even when
Historyis gone. - Cache — cached responses carry their original request URLs.
- Bookmarks — explicit, user-curated URLs.
- Session restore — the last open tabs, with URLs.
- LevelDB old SSTs — superseded
.ldb/.sstfiles retain prior Local Storage and IndexedDB keys, many of them origin-scoped.
See Top Sites and Favicons and browser cache forensics for what each one actually stores.
Putting it together
- Load
Historyplus its-wal(and-shm). Read the merged table — that recovers checkpointed and committed-but-unmerged rows in one step. - If rows are still missing, carve the freelist and unallocated pages with a dedicated SQLite tool.
- Cross-check Top Sites, Favicons and Cache for domains the database no longer lists. Pull Bookmarks and session restore for explicit URLs.
- Build a unified timeline from everything recovered, label each entry with its source artifact, and export to CSV / JSON.
State your confidence per source. A row carved from the WAL is strong evidence; a domain inferred only from a favicon is weaker and should be flagged as such. And say plainly when pages have been overwritten beyond recovery — that, too, is a finding.