- Result code
- SQLITE_READONLY (8); extended codes include SQLITE_READONLY_DIRECTORY (1544) and SQLITE_READONLY_DBMOVED (1032)
- Product
- SQLite library
- Versions
- SQLite 3.x (examples run on 3.39.2; shell output from 3.50.6)
- Category
- Files & Permissions
- Last reviewed
- 24 October 2026
On this page
What it means
This error means SQLite opened the database but is not allowed to change it, because the connection is read-only, the file or its folder is not writable, or writes were switched off.
SQLite raises SQLITE_READONLY when a statement tries to change a database that the connection may only read. The connection can be read-only on purpose (opened with SQLITE_OPEN_READONLY, a mode=ro URI, sqlite3 -readonly, or PRAGMA query_only = ON) or by accident: when SQLite cannot open the file for writing because of operating system permissions, it opens it read-only instead of failing, so the open succeeds and the error appears only at the first write. We saw this on Windows with a file that had the read-only attribute set.
Writing also needs the folder. In rollback-journal mode SQLite creates a -journal file next to the database for every write transaction, and in WAL mode it needs the -wal and -shm files, so a writable file in a folder the process cannot write to still fails (SQLITE_READONLY_DIRECTORY, 1544). That is the usual cause on Linux web servers, where the database is writable but its folder belongs to another user. If the file was moved or renamed while open, SQLite refuses further writes with SQLITE_READONLY_DBMOVED (1032). Most drivers show only 8; the extended codes need a driver that exposes them.
Common causes
- The connection was opened read-only: a read-only flag in the driver,
?mode=roin a URI filename,sqlite3 -readonly, orimmutable=1. - PRAGMA query_only = ON was set on the connection, for example by a reporting tool or a framework's read replica setting.
- The operating system does not let the process write the file: wrong owner (a web server user such as www-data cannot write a file created by your login), read-only file permissions or attribute, or read-only media such as an app bundle or a mounted image.
- The folder that holds the database is not writable, so SQLite cannot create the journal, WAL or shared-memory files.
- The database file was moved, replaced or deleted while a connection still had it open.
Diagnose it
Read-only These statements only read the schema (sqlite_schema and read-only PRAGMA queries) or your own data. They do not change data, settings or the database file, so they are safe to run first.
Is the connection in query-only mode?
PRAGMA query_only; 1 means this connection refuses all changes, including CREATE TEMP TABLE. 0 means the restriction comes from how the file was opened or from the operating system.
Which file is the connection using?
PRAGMA database_list; The full path of the main database and any attached ones. Check the permissions of exactly this file and its folder; a relative path may point somewhere you did not expect.
Which journal files does a write need?
PRAGMA journal_mode; delete, truncate or persist: a write creates or uses shop.db-journal next to the file. wal: it needs shop.db-wal and shop.db-shm. The process must be able to create these in the folder.
Check file and folder permissions for the user that runs the code
On Linux or macOS, run ls -l /path/to/shop.db* and ls -ld /path/to and compare the owner and permissions with the user that runs your application (for a web server, often www-data or apache; check with ps). With SELinux, also check the security context with ls -Z. On Windows, check the file's Read-only attribute and the folder's security settings for that account.
How to fix it
Each fix is labelled with what it changes. Try read-only checks and the least invasive fix first, and test changes outside production.
Open the database read-write
Client or network sideRemove the read-only option where the connection is created: the read-only open flag in PHP (PDO::SQLITE_OPEN_READONLY or SQLITE3_OPEN_READONLY), mode=ro or immutable=1 in a URI filename (use mode=rw, or mode=rwc to allow creating it), or the -readonly option of the sqlite3 shell.
Turn query-only mode off for this connection
Changes configurationIf the connection was put into query-only mode deliberately (for a reporting user, for example), write through a different connection instead of switching it off.
PRAGMA query_only = OFF;
Give the application user write access to the file and its folder
Changes permissionsMake the user that runs the code the owner of the database file and of the folder that holds it, or put both in a group the application belongs to, for example sudo chown www-data:www-data /var/lib/myapp /var/lib/myapp/shop.db and chmod 770 on the folder, 660 on the file. Include any existing -wal, -shm or -journal files. Keep the database out of the public web root.
Move the database to a writable location
Client or network sideFiles shipped inside an application (a program folder, an app bundle, Android assets, a read-only container image) are not writable at run time. On first start, copy the database to the platform's data folder (for example %LOCALAPPDATA% on Windows, the app's files directory on Android, a mounted volume in a container) and open the copy. For data that is only ever read from read-only media, open it with immutable=1 and convert it to rollback-journal mode before shipping, as the WAL documentation advises.
Examples
Writing through a connection opened read-only
-- shop.db already contains: CREATE TABLE orders (id INTEGER PRIMARY KEY, total REAL);
-- the connection is opened read-only (SQLITE_OPEN_READONLY, mode=ro or sqlite3 -readonly)
SELECT COUNT(*) FROM orders; -- reading works
INSERT INTO orders (total) VALUES (25);
attempt to write a readonly database
Reproduced on SQLite 3.39.2, 24 October 2026.
Writing after PRAGMA query_only = ON
CREATE TABLE orders (id INTEGER PRIMARY KEY, total REAL);
PRAGMA query_only = ON;
INSERT INTO orders (total) VALUES (25);
attempt to write a readonly database
Reproduced on SQLite 3.39.2, 24 October 2026.
The folder that holds the database is not writable (Linux)
Shown as documented in the SQLite documentation; not reproduced here, because the lab runs on Windows, where Unix folder permissions cannot be set. The extended code is SQLITE_READONLY_DIRECTORY (1544), described in SQLite result and error codes.
-- shop.db is writable by the application user, but /var/lib/myapp is not
-- (for example owned by root with mode 755)
INSERT INTO orders (total) VALUES (25); -- needs to create shop.db-journal in that folder
attempt to write a readonly database
How to prevent it
Decide where the database lives at deployment time: a folder outside the web root that the application user owns, with the database and its journal files inside it. Create the file as that user (or fix ownership after copying it in), and run any migration or seeding scripts as the same user so they do not leave root-owned files behind.
Use read-only connections deliberately for reporting or untrusted queries, and give them a name in your code so a write path never picks one up by mistake. Do not copy, rename or replace a database file while connections are open; stop the application or use the backup API (VACUUM INTO or the sqlite3 shell's .backup) instead.
Common questions
How do I fix "attempt to write a readonly database" in SQLite?
Check PRAGMA query_only and how the connection was opened (a read-only flag or mode=ro). If neither applies, give the user that runs your code write permission on the database file and on the folder that contains it, because SQLite creates journal files there for every write.
The file is writable, so why does SQLite still say it is read-only?
Writes also need the folder: SQLite creates the -journal file (or the -wal and -shm files in WAL mode) next to the database. If the process cannot create files in that folder, the database is read-only for it. Give the application user write access to the folder too.
Why did opening the database succeed if I cannot write to it?
When SQLite cannot open a file for writing because of operating system permissions, it falls back to read-only instead of failing. Reads work, and the error appears at the first INSERT, UPDATE, DELETE or CREATE. If the file cannot be read at all, you get "unable to open database file" instead.
Learn more on CodeWithSQL
Official documentation
- SQLite result and error codes (SQLITE_READONLY_*)
- Opening a database: sqlite3_open_v2 flags
- URI filenames (mode=ro, immutable)
- PRAGMA query_only
Last technically reviewed 24 October 2026; examples and diagnostics run on SQLite 3.39.2 (PHP PDO_SQLITE) and the sqlite3 shell 3.50.6. Messages are the text the SQLite library itself returns (sqlite3_errmsg) for the versions shown.