Short answer: SQLite is a library, not a server, so there is no service to start. On Ubuntu or Debian, sudo apt install sqlite3 adds the sqlite3 command-line shell. On Windows, run winget install --id SQLite.SQLite -e or unzip the sqlite-tools bundle from sqlite.org. Then sqlite3 app.db opens a database file, creating it if needed.
sudo apt update
sudo apt install -y sqlite3
sqlite3 --version
sqlite3 app.dbThe Python, WAL, locking and permission results on this page were run on Debian 12 with Python 3.11.2 and its SQLite 3.40.1 library in a scratch directory on 21 October 2026. The sqlite3 shell itself is not installed on that host, so the shell sessions follow the sqlite.org CLI documentation and are marked as examples.
How do I install SQLite on Ubuntu and Debian?
The sqlite3 package is only the command-line shell. The library it uses, libsqlite3-0, is usually installed already, because Python and many other packages depend on it. Add libsqlite3-dev only when you compile software against SQLite. Versions in the archives, as listed on packages.debian.org and Launchpad on 21 October 2026:
| Release | sqlite3 package | Default output of the shell |
|---|---|---|
| Ubuntu 22.04 | 3.37.2 | list, values separated by | |
| Debian 12 | 3.40.1 | list, values separated by | |
| Ubuntu 24.04 | 3.45.1 | list, values separated by | |
| Debian 13 | 3.46.1 | list, values separated by | |
| sqlite.org (16 October 2026) | 3.54.1 | Boxes in interactive sessions (since 3.53.0), list in scripts |
Every one of these reads and writes the same file format, so a database made on one works on all of them. On Rocky Linux and AlmaLinux the package is called sqlite: sudo dnf install sqlite. For a newer shell than your distribution has, sqlite.org also offers a sqlite-tools-linux-x64 zip with a ready-made binary.
How do I install SQLite on Windows?
With winget, in PowerShell or Command Prompt:
winget install --id SQLite.SQLite -e
# open a new terminal, then
sqlite3 --versionThe SQLite.SQLite package installs the official tools zip as portable commands: sqlite3, sqldiff, sqlite3_analyzer and sqlite3_rsync. Its manifest can lag behind sqlite.org; on 21 October 2026 the newest one in the winget repository was 3.53.4 while sqlite.org already had 3.54.1. Windows Server 2022 and 2019 ship without winget; how to add it is in installing winget on Windows Server.
By hand, download sqlite-tools-win-x64-3540100.zip from the “Precompiled Binaries for Windows” section of the sqlite.org download page (the number encodes the version, 3.54.1 here). The sqlite-dll zips are only for programs that load sqlite3.dll; the shell is in the tools zip. Unpack it and put the folder on the system PATH, in PowerShell as Administrator:
Expand-Archive .\sqlite-tools-win-x64-3540100.zip -DestinationPath C:\sqlite
$p = [Environment]::GetEnvironmentVariable('Path', 'Machine')
[Environment]::SetEnvironmentVariable('Path', "$p;C:\sqlite", 'Machine')
# open a new terminal, then
sqlite3 --versionThe sqlite3 shell: first steps
Lines that start with a dot are commands of the shell itself; everything else is SQL and runs when you end it with a semicolon:
$ sqlite3 app.db
sqlite> CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE);
sqlite> INSERT INTO users (name, email) VALUES ('alice', 'alice@example.com'), ('bob', 'bob@example.com');
sqlite> SELECT * FROM users;
1|alice|alice@example.com
2|bob|bob@example.com
sqlite> .tables
users
sqlite> .schema users
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE);
sqlite> .quit| Task | Command |
|---|---|
| Open or switch to a database file | .open app.db |
| Open it without any chance of writing | .open --readonly app.db |
| List tables | .tables |
| Show the CREATE statements | .schema [table] |
| Show column names in output | .headers on |
| Change the output format | .mode column | box | table | csv | json | markdown | list |
| Load a CSV file into a table | .import --csv --skip 1 users.csv users |
| Send the next result to a file | .once out.csv |
| Export everything as SQL | .dump |
| Run SQL from a file | .read schema.sql |
| Consistent copy of a live database | .backup backup.db |
| Wait instead of failing on a lock | .timeout 5000 |
| Time every statement | .timer on |
| All commands | .help |
Output modes: column, CSV and JSON
sqlite> .headers on
sqlite> .mode column
sqlite> SELECT id, name FROM users;
id name
-- -----
1 alice
2 bob
sqlite> .mode csv
sqlite> SELECT id, name FROM users;
id,name
1,alice
2,bob
sqlite> .mode json
sqlite> SELECT id, name FROM users;
[{"id":1,"name":"alice"},
{"id":2,"name":"bob"}]The same works without entering the shell, which is handy in scripts and cron jobs: sqlite3 -header -csv app.db "SELECT * FROM users;" > users.csv. From version 3.53.0 on, an interactive shell starts in a box layout and right-aligns numbers, while scripts and pipes keep the old list format, so output parsed by a script does not change when you upgrade.
Importing CSV
.import --csv users.csv users behaves differently depending on whether the table exists. If it does not, SQLite creates it and takes the column names from the first row. If it does, every row is data, including a header line, so add --skip 1. Columns that .import creates are declared as TEXT; for real types, create the table first.
Dump, restore and backup
# SQL text: readable, diffable, works across versions
sqlite3 app.db .dump > app.sql
sqlite3 restored.db < app.sql
# a binary copy that stays consistent while the application writes
sqlite3 app.db ".backup '/var/backups/app-$(date +%F).db'"Do not copy a busy database with cp: a write in the middle of the copy, or changes still sitting in the -wal file, leave you with an old or broken copy. .backup uses SQLite’s online backup API and VACUUM INTO 'file' writes a compacted copy; both are safe while the application runs.
WAL mode: readers and a writer at the same time
By default SQLite uses a rollback journal (journal_mode reads delete), and a writer blocks readers while it commits. Write-ahead logging changes that, and for a web application it is nearly always the better choice:
sqlite> PRAGMA journal_mode=WAL;
wal- Persistent: the setting is stored in the database file. Set it once and every later connection uses WAL. In our Python test the pragma returned
wal, and a fresh connection still reported WAL. - Concurrent reads: in our test a second connection read the table while the first held an open write transaction.
- Still one writer: a second writer with no timeout failed at once with
database is locked. Give every connection a busy timeout (PRAGMA busy_timeout=5000;) so it waits instead of failing. - Two extra files:
app.db-walandapp.db-shmappear next to the database. When the last connection closed, both were folded back and removed. - Same machine only: WAL needs shared memory between processes, so it does not work over NFS or other network filesystems.
Two more defaults surprise people: foreign keys are not enforced unless each connection runs PRAGMA foreign_keys=ON;, and synchronous is FULL (both confirmed on Debian 12). With WAL, the SQLite documentation describes PRAGMA synchronous=NORMAL; as safe from corruption, at the cost of possibly losing the last commits on a power failure.
File permissions for web applications
SQLite creates its journal, WAL and shared-memory files next to the database, so the process needs write access to the directory, not only to the file. We reproduced these cases with a process that lacked the rights in question:
| Situation | Error |
|---|---|
| Database file not writable | attempt to write a readonly database |
| File writable, directory not (rollback or WAL mode) | attempt to write a readonly database |
| WAL database, directory not writable, no -shm file yet: even a SELECT | attempt to write a readonly database |
| New database in a directory you cannot write | unable to open database file |
Give the application its own data directory, outside the web root, owned by the user the application runs as:
sudo install -d -o www-data -g www-data -m 750 /var/lib/myapp
sudo mv /var/www/html/app.db /var/lib/myapp/app.db
sudo chown www-data:www-data /var/lib/myapp/app.db
sudo chmod 640 /var/lib/myapp/app.dbA database file inside /var/www/html can be downloaded by anyone who guesses its name, which is reason enough to move it. More on owners and modes in the chown and chmod guides.
Is SQLite enough on a server?
Often, yes. The SQLite project says it “works great as the database engine for most low to medium traffic websites” and puts a conservative figure on it: a site with fewer than 100,000 hits a day. Where it stops fitting is concurrency and the network:
| SQLite | PostgreSQL / MariaDB | |
|---|---|---|
| Runs as | A library inside your application | A separate server process |
| Writers | One at a time; others wait for the lock | Many in parallel, with row-level locking |
| Access from other machines | None; the file must be local | Over TCP, with users and passwords |
| Permissions | File system only | Roles, grants per database and table |
| Setup and upkeep | Nothing to run, patch or tune | A service to configure, secure and update |
| Backup | .backup or VACUUM INTO, one file | pg_dump or mariadb-dump |
| Good fit | One app on one server, mostly reads: blogs, bots, dashboards, internal tools | Several app servers, heavy concurrent writes, shared data |
If several application servers need the same data, or many requests write at the same instant, use a client/server database: installing PostgreSQL and installing MariaDB or MySQL cover both. Many self-hosted tools start on SQLite and offer PostgreSQL for larger installs, as n8n does.
SQLite from Python
Python ships the sqlite3 module in its standard library, so there is nothing to install with pip. It uses the system’s SQLite library:
import sqlite3
con = sqlite3.connect("app.db") # timeout defaults to 5 seconds
con.execute("PRAGMA journal_mode=WAL")
con.execute("PRAGMA foreign_keys=ON")
with con: # commits, or rolls back on an exception
con.execute("INSERT INTO users (name, email) VALUES (?, ?)", ("carol", "carol@example.com"))
for row in con.execute("SELECT id, name FROM users WHERE name = ?", ("alice",)):
print(row)
print(sqlite3.sqlite_version)
con.close()(1, 'alice')
3.40.1Always pass values as ? parameters, never with string formatting, which invites SQL injection. The with con: block commits but does not close the connection. Python’s default timeout of 5 seconds becomes a busy timeout of 5000 ms, which we confirmed with PRAGMA busy_timeout. If import sqlite3 fails with No module named '_sqlite3', that Python was compiled without the SQLite headers: install libsqlite3-dev and rebuild it, or use the distribution’s Python. For packages that do come from PyPI, see installing pip.
Common SQLite errors
| Message | Cause | Fix |
|---|---|---|
sqlite3: command not found | Only the library is installed | sudo apt install sqlite3 |
no such table: users | Usually the wrong file: a relative path opened a new, empty database in another directory | Use an absolute path to the database |
database is locked | Another connection holds the write lock | WAL mode, a busy timeout, short transactions |
attempt to write a readonly database | No write access to the file or its directory | Fix owner and mode of both, as above |
unable to open database file | The directory does not exist or is not writable | Create it with the right owner |
file is not a database | Not an SQLite file, or an encrypted one | Check the path and what wrote the file |
SQLite on a VPS
On a server, SQLite turns the database into one file next to your application, with no port to firewall and no service to patch. The backup routine is a dated .backup from cron plus a copy to another machine. On a VMHeaven Standard KVM server the disk sits on replicated Ceph storage, and panel snapshots, taken by hand or on a schedule, cover the whole system before an upgrade; for the database itself the .backup file is still the copy to restore from. On the Ubuntu and Debian images the apt route above applies, and a Windows VPS runs Windows Server 2022 or 2019, which ship without winget, so take the sqlite-tools zip route by hand there, or add winget first.
Frequently asked
Does SQLite need a server or a service?
No. SQLite is a library that reads and writes a single database file directly, so there is nothing to start, no port and no user accounts. 'sudo apt install sqlite3' only adds the command-line shell; programs use the libsqlite3 library or a driver such as Python's built-in sqlite3 module.
How do I open a SQLite database from the command line?
Run 'sqlite3 path/to/app.db'. It opens the file, or creates it if it does not exist, so check the path. Inside the shell, '.tables' lists the tables, '.schema' shows their definitions, '.headers on' with '.mode column' makes results readable, and '.quit' leaves.
How do I export a SQLite table to CSV?
From the shell prompt run: sqlite3 -header -csv app.db "SELECT * FROM users;" > users.csv. Inside the sqlite3 shell, '.headers on', '.mode csv' and '.once users.csv' followed by the SELECT do the same. '.import --csv --skip 1 users.csv users' loads a CSV file back into an existing table.
Can several processes write to SQLite at the same time?
Only one at a time. In WAL mode (PRAGMA journal_mode=WAL) readers keep reading while a write is in progress, but a second writer has to wait for the lock, and without a busy timeout it fails at once with 'database is locked'. Set PRAGMA busy_timeout, keep transactions short, and move to PostgreSQL or MariaDB when many requests write at the same moment.