SQLite raises SQLITE_READONLY_DBMOVED when it detects that the database file has been moved or renamed after a connection was opened, so it refuses all writes to guard against rollback journal corruption.
SQLITE_READONLY_DBMOVED is an extended SQLITE_READONLY status that fires when SQLite recognizes the database file was relocated after the handle was opened. Because the rollback journal lives next to the database file and uses the same basename, moving or renaming the file while a connection remains active could leave the journal pointing to a stale path when a crash occurs. SQLite responds by locking the database in read-only mode until every connection closes and reopens the file at the new location, ensuring the journal stays in sync with the database name.
Use OS tools to inspect the open file path and detect renames. On Linux/macOS you can run:
PID=$(pgrep -f my-app)
lsof -p $PID | grep database.db
stat database.dbRe-run stat or ls immediately after your deployment script finishes and compare the inode. If the inode changes while the handle stays active, SQLite will classify the database as moved and block writes. On Windows, use Process Explorer or the Handle utility to inspect which process keeps the file open.
Wrap your rename/copy logic so every connection closes first. For example, with node-sqlite3:
await db.close();
fs.renameSync(tempPath, mainPath);
const reopened = new sqlite3.Database(mainPath, sqlite3.OPEN_READWRITE);Closing ensures SQLite knows the file move happened, so the new handle opens the database at the new path instead of seeing it as moved.
If an update must replace the file, discard the old Database object and create a new one after the rename. Always call sqlite3_close_v2 (or the async equivalent) on every connection before moving the file, then call sqlite3_open again once the file sits in its new place.
When you need to refresh data without stopping the app, keep the live file untouched and write to a temporary database instead:
1. Use sqlite3_backup_init/sqlite3_backup_step to copy data into a temporary file while the main connection stays open.
2. After the backup completes, close all connections, rename the temporary file into place, and restart your app.
3. Alternatively, run VACUUM INTO 'new.db' to produce a new copy without deleting the old one until you are ready to swap.
If multiple nodes share a mounted volume (NFS, SMB, etc.), rewrite the deployment so each node uses a local database or a client-server database. SQLite lacks multi-writer coordination, so moving the shared file while writers are active will always trigger SQLITE_READONLY_DBMOVED. Keep the file path stable and only replicate data via controlled snapshots or migrations.
SQLite only raises SQLITE_READONLY_DBMOVED when it detects the on-disk path no longer points at the file that was originally opened. The rollback journal name is derived from that path, and if the file is moved the journal would go out of sync and risk corruption if a write is interrupted. Closing and reopening the connection tells SQLite about the new path so writability can resume.
SQLITE_CORRUPT_VTAB: Content in virtual table is corrupt
Content in virtual table is corrupt
SQLITE_IOERR_WRITE: Disk I/O error during write
Disk I/O error during write operation
SQLITE_READONLY: Attempt to write a readonly database
How to fix "SQLITE_READONLY: Attempt to write a readonly database" in SQLite
SQLITE_CONSTRAINT_PRIMARYKEY: PRIMARY KEY constraint failed
How to fix "SQLITE_CONSTRAINT_PRIMARYKEY" in SQLite
SQLITE_IOERR_SHORT_READ: Read returned less data than requested
How to fix 'SQLITE_IOERR_SHORT_READ: Read returned less data than requested' in SQLite