Guides

How to install SQLite on Ubuntu, Debian and Windows, and use the sqlite3 shell

Install SQLite with apt install sqlite3 or winget on Windows, then use the sqlite3 shell (.tables, .mode, .import, .backup), WAL mode and Python's sqlite3.

Published ~9 min read
On this page

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.

Ubuntu · Debian
sudo apt update
sudo apt install -y sqlite3
sqlite3 --version
sqlite3 app.db

The 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:

Releasesqlite3 packageDefault output of the shell
Ubuntu 22.043.37.2list, values separated by |
Debian 123.40.1list, values separated by |
Ubuntu 24.043.45.1list, values separated by |
Debian 133.46.1list, values separated by |
sqlite.org (16 October 2026)3.54.1Boxes 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:

Windows 10 · 11 · Server 2025
winget install --id SQLite.SQLite -e
# open a new terminal, then
sqlite3 --version

The 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:

PowerShell (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 --version

The 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:

Example session, list mode (shell versions before 3.53)
$ 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
TaskCommand
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

Example output
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

shell
# 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:

Example session
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-wal and app.db-shm appear 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:

SituationError
Database file not writableattempt 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 SELECTattempt to write a readonly database
New database in a directory you cannot writeunable to open database file

Give the application its own data directory, outside the web root, owned by the user the application runs as:

Debian · Ubuntu, app running as www-data
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.db

A 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:

SQLitePostgreSQL / MariaDB
Runs asA library inside your applicationA separate server process
WritersOne at a time; others wait for the lockMany in parallel, with row-level locking
Access from other machinesNone; the file must be localOver TCP, with users and passwords
PermissionsFile system onlyRoles, grants per database and table
Setup and upkeepNothing to run, patch or tuneA service to configure, secure and update
Backup.backup or VACUUM INTO, one filepg_dump or mariadb-dump
Good fitOne app on one server, mostly reads: blogs, bots, dashboards, internal toolsSeveral 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:

app.py
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()
Output on Debian 12, Python 3.11.2
(1, 'alice')
3.40.1

Always 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

MessageCauseFix
sqlite3: command not foundOnly the library is installedsudo apt install sqlite3
no such table: usersUsually the wrong file: a relative path opened a new, empty database in another directoryUse an absolute path to the database
database is lockedAnother connection holds the write lockWAL mode, a busy timeout, short transactions
attempt to write a readonly databaseNo write access to the file or its directoryFix owner and mode of both, as above
unable to open database fileThe directory does not exist or is not writableCreate it with the right owner
file is not a databaseNot an SQLite file, or an encrypted oneCheck 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.

Related articles

All guides