n8n Database Not Shrinking After Pruning? SQLite, WAL and Disk Space

You pruned thousands of executions from n8n's history, yet database.sqlite is the same size and the disk is still nearly full. Where did the freed space go?

n8n Database Not Shrinking After Pruning? SQLite, WAL and Disk Space
TLDR
  • Pruning can work while database.sqlite stays the same size. Deleted rows leave pages SQLite can reuse; they do not necessarily return disk space to the operating system.
  • A large database.sqlite-wal is a separate problem. Check the database file, its free pages and the WAL before choosing a fix.
  • Back up first. Reclaim space in a maintenance window, then reduce saved execution data so the file does not immediately grow again.

You deleted thousands of executions. The history is shorter, but the hosting dashboard still says the disk is nearly full. Buying a larger VPS is not the first thing to do: find out whether the occupied space is live data, reusable database pages or a write-ahead log.

That distinction explains a community storage question in which deleting executions left usage unchanged. It also avoids the much worse response described by a Reddit operator whose volume filled up: removing the container in frustration and losing the workflows and authentication setup.

Pruning removes history; VACUUM repacks the file

n8n's execution-data guide says pruning is enabled by default. Age and count limits determine which eligible executions are removed. The same guide explains that SQLite normally keeps the freed space available for future writes.

Think of the file as allocated pages rather than a count of visible executions. Deleting records can empty pages without reducing that allocation. A successful cleanup and an unchanged file size are therefore compatible outcomes.

SQLite's VACUUM operation rebuilds the database more compactly. It is a different operation from deciding which execution records to retain. You need both a sensible retention policy and, when physical space must be returned, an appropriate compaction plan.

Measure three things before changing the server

ObservationWhat it suggestsNext check
Main database is large; many free pagesSQLite has space it can reuse internallyWhether a controlled VACUUM is worth the downtime and temporary space
Main database is large; few free pagesMuch of the allocation is still in useRetention, payload size and whether eligible executions have actually been removed
WAL is much larger than the main databaseUncheckpointed writes may dominate disk usageLong-running readers, write load and checkpoint behavior
Database is modest; volume is fullThe database may not be the main consumerBinary files, container logs, local backups and other files on that volume

Find the real database path in your volume mapping. Do not assume the path inside a container is also the path on its host. With the SQLite command-line tool available, these are inspection commands:

sqlite3 -readonly /path/to/database.sqlite \
  'PRAGMA page_size; PRAGMA page_count; PRAGMA freelist_count;'
ls -lh /path/to/database.sqlite*

SQLite's page statistics give a useful approximation: page_size × page_count describes the database allocation, while page_size × freelist_count counts entirely free pages. This is not a count of all empty bytes inside partly filled pages. Compare it with the file sizes rather than treating the history screen as a disk report.

A storage test: 95% fewer rows, the same file size

We created a disposable SQLite 3.53.4 database containing 20,000 rows with 2,048-byte payloads. We deleted 19,000 rows, then ran VACUUM on a separate copy. This tests the storage mechanism behind the pruning question, rather than n8n's pruning scheduler.

StageRows retainedFile sizeEntirely free pages
Before deletion20,00078.31 MiB0
After deletion1,00078.31 MiB19,043
After VACUUM on the copy1,0003.92 MiB0

The deletion reduced the data, not the file. The remaining 1,000 records survived compaction. A hosting panel that only displayed file size would have missed the first successful operation entirely.

A growing WAL needs its own diagnosis

In WAL mode, writes go into the -wal file before checkpointing transfers changes to the main database. A long-running reader can prevent a checkpoint from finishing or the WAL from resetting. SQLite's WAL documentation describes that reader/checkpoint interaction.

In a second disposable test, we kept a read transaction open while another connection wrote data. The WAL grew to 8,277,112 bytes. A truncate checkpoint reported it was busy. After ending the reader's transaction, the same operation completed and the WAL reached zero bytes; the main database grew as the changes were checkpointed.

That result matters because a smaller WAL does not imply that the same number of bytes disappeared from the combined dataset. Some data moves into the main file. A reported n8n WAL-growth issue also distinguishes a large WAL from its smaller main database. The issue is a diagnostic lead, not proof that every slow installation has the same cause.

Do not delete the WAL to make the disk report smaller. It can contain committed changes that have not reached the main file. Preserve a consistent backup and deal with the connections or checkpoint conditions instead.

Reclaim space without turning cleanup into data loss

  1. Identify the database, WAL and actual storage volume. Check available working space before attempting compaction.
  2. Make a consistent recovery copy, including the credential encryption key and required files. Use the n8n backup and restore checklist rather than copying an actively changing database file alone.
  3. Schedule a maintenance window and stop workflow traffic and database writers using your deployment's normal controls.
  4. Choose a supported compaction route. n8n documents DB_SQLITE_VACUUM_ON_STARTUP=true; the database settings guide warns that it blocks startup and increases startup time.
  5. After the controlled startup, verify workflows, credential-backed requests and storage. Reset the option if you do not intend to repeat that work on every start.

VACUUM needs temporary working space; a disk with no headroom can prevent the repair you are trying to run. Restore and test the copy elsewhere if there is not enough room. Do not use a PostgreSQL maintenance command against SQLite, or assume SQLite's startup setting maintains a PostgreSQL database.

Stop saving data you do not need

Compaction only addresses space already allocated. The execution settings control what enters the history and how long it remains. For a workload that does not need successful execution payloads, a configuration review might include:

EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168
EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
DB_SQLITE_VACUUM_ON_STARTUP=true

The 168-hour value is a seven-day example, not a requirement. Keep the evidence needed for troubleshooting and business recovery, and check workflow-level overrides. Running, waiting and annotated executions can also explain why an expected cleanup has not removed everything. The VACUUM flag in that example is a maintenance choice, not a retention limit.

For file-heavy workflows, inspect the configured binary-data storage as well. Moving future files to another storage mode does not automatically clean every old location.

Upgrade storage when the data you genuinely need exceeds the available capacity—not simply because pruning left a large SQLite file. If concurrency and workload growth remain the problem after retention is under control, compare a database or hosting change using measured load, not file size alone.