Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Sqlite Turso Ubuntu
GUIDEInstall Guides

How to Install SQLite and Self-Hosted libSQL (Turso) on Ubuntu 24.04

23 min read

How to Install SQLite and Self-Hosted libSQL (Turso) on Ubuntu 24.04

SQLite is the most widely deployed database engine in the world. It ships inside every Android phone, every iOS device, every copy of Firefox and Chrome, every Python install, and inside aircraft and Bloomberg terminals. Despite that scale, most server developers still reach for Postgres or MySQL by default -- even for workloads where SQLite would be faster, simpler, and free of operational overhead. This guide walks you through installing SQLite on Ubuntu 24.04, configuring it for production-grade web workloads, replicating it continuously with Litestream, and upgrading to a full Turso-compatible remote database by running the open-source libSQL server (sqld) on your own VPS.

Skip the setup? Deploy a ready-to-use SQLite + libSQL VPS with WAL tuning, Litestream backups, and sqld pre-configured. Launch a CloudCore Starter now and be writing queries in 60 seconds.

Table of Contents

  • What is SQLite?
  • Why SQLite Beats "Real" Databases for Many Workloads
  • Prerequisites
  • Step 1: Update System Packages
  • Step 2: Install sqlite3 and sqlite-utils
  • Step 3: First Database and SQL Basics
  • Step 4: Production PRAGMAs (WAL and Friends)
  • Step 5: Live Backups
  • Step 6: Continuous Replication with Litestream
  • Step 7: Explore Data with Datasette
  • Step 8: Install libSQL Server (sqld) for Turso-Compatible Access
  • Step 9: Secure sqld with TLS and JWT Auth
  • Step 10: Embedded Replicas from Client Apps
  • rqlite: A Distributed Alternative
  • Troubleshooting
  • FAQ
  • Next Steps
  • What is SQLite?

    SQLite is a C-language library that implements a small, fast, self-contained, full-featured SQL database engine. Unlike Postgres or MySQL, SQLite is not a separate server process -- it runs inside your application as a linked library, reading and writing directly to a single file on disk. There is no daemon to start, no port to listen on, no user accounts to manage, and no network round-trip. The entire database -- schema, tables, indexes, triggers, and views -- lives in one portable file that you can copy, version control, attach to an email, or check into S3.

    SQLite is explicitly in the public domain and is one of the four formats recommended by the US Library of Congress for long-term data preservation. It is embedded in essentially every smartphone, every major browser, every Mac and Windows PC (via system libraries), every Python and Ruby install, and inside devices as varied as Tesla cars, Airbus A350 avionics, and Bloomberg trading terminals. Estimates put the number of deployed SQLite instances well past one trillion, making it the most widely used database software in history by a wide margin.

    Use cases where SQLite shines:

    • Embedded applications -- Mobile apps, desktop apps, browser storage, IoT devices, edge gateways.
    • Development and testing -- Zero-setup dev environments and fast, deterministic test fixtures.
    • Low-to-medium-concurrency web apps -- Blogs, internal tools, admin panels, SaaS apps with a single writer and many readers.
    • Data analysis and ETL -- Ingest CSV/JSON, run SQL against it, export results. sqlite-utils and Datasette make this workflow a joy.
    • Cache and session stores -- Fast, persistent local state without a Redis dependency.
    • Application file format -- When a file needs structured, queryable, transactional storage (think Photos libraries, .sketch files, Fossil repos).

    Why SQLite Beats "Real" Databases for Many Workloads

    The historic wisdom "SQLite is fine for dev, use Postgres in prod" deserves a second look. A wave of production systems -- some of the biggest on the internet -- are built on SQLite:

    • Cloudflare D1 is a serverless, globally distributed database built on SQLite (via libSQL-style storage) and serves billions of queries a month across Cloudflare's edge network.
    • Fly.io hosts thousands of production apps that run SQLite on a local volume with Litestream or LiteFS replication instead of a managed Postgres.
    • Amazon Bedrock and the Expensify backend both famously built SQLite-backed systems that scale to the kind of load you would "obviously" use a real database for, and beat the pants off it.
    • Tailscale's control plane and parts of Notion's stack have leaned on embedded SQLite for hot paths.
    The case is straightforward. If your writes are low-to-moderate (say, a few hundred per second) and your reads are high, SQLite running in-process on the same machine as your web server will beat a networked Postgres hands-down on latency, because there is no network. A round-trip to a Postgres replica is 0.5-2 ms. A SQLite SELECT from WAL-mode cache is a few microseconds. Multiply that by every query on a page render and the difference compounds.

    SQLite is also incredibly cheap to operate. No replicas to keep in sync, no connection pooler, no pg_hba.conf, no shared_buffers tuning, no role/grant dance, no upgrade pain. Your backup is a file. Your rollback is cp backup.db app.db.

    The classic objection -- "SQLite can't handle concurrent writes" -- is mostly wrong with WAL mode. A single SQLite database supports one writer at a time, but that writer does not block readers, and with WAL enabled it can sustain thousands of writes per second on modern NVMe. The workloads where SQLite genuinely breaks down are: multi-writer sharded systems, OLAP over hundreds of GB, and apps that need synchronous multi-node durability. Everything else is fair game.

    Prerequisites

    Before you begin, make sure you have:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access
    • SSH access to your server
    • 2 GB+ RAM (SQLite itself is tiny; this is for your app and libSQL)
    • 20 GB+ SSD storage (NVMe strongly recommended -- SQLite performance is I/O bound)
    Recommended Plan: CloudCore Starter
    >
    SQLite's minimal footprint means you do not need a huge VPS. For most SQLite + libSQL workloads we recommend CloudCore Starter:
    >
    - 2 vCPU cores
    - 4 GB RAM
    - 50 GB NVMe SSD
    - Unmetered bandwidth
    >
    This is more than enough for a SQLite-backed web app with Litestream replication and a sqld listener. Scale up to CloudCore Professional only if you are running analytics queries over tens of GB.

    Connect to your server via SSH:

    bash
    ssh root@your-server-ip

    Step 1: Update System Packages

    Refresh your package index and apply any pending upgrades. SQLite moves quickly and Ubuntu 24.04 ships a fairly recent release in its repos, but always start from a clean baseline.

    bash
    sudo apt update && sudo apt upgrade -y

    If the kernel was updated, reboot:

    bash
    sudo reboot

    Step 2: Install sqlite3 and sqlite-utils

    Ubuntu's default repos include the sqlite3 CLI and its shared library. Install them:

    bash
    sudo apt install -y sqlite3 libsqlite3-dev

    Verify the version:

    bash
    sqlite3 --version

    Expected output:

    text
    3.45.1 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a

    Next install sqlite-utils, Simon Willison's Swiss-army-knife companion CLI. It gives you one-liners for inserting CSV/JSON, extracting columns into lookup tables, running analysis queries, and a superb in-memory mode for ad-hoc data work.

    The cleanest install on Ubuntu 24.04 is via pipx, which avoids the externally-managed-environment error from system pip:

    bash
    sudo apt install -y pipx
    pipx ensurepath
    pipx install sqlite-utils

    Close and reopen your SSH session (so ~/.local/bin is on PATH) and verify:

    bash
    sqlite-utils --version

    Expected output:

    text
    sqlite-utils, version 3.36

    Step 3: First Database and SQL Basics

    Create your first database. SQLite has no CREATE DATABASE -- you just open a filename and it appears:

    bash
    mkdir -p ~/sqlite && cd ~/sqlite
    sqlite3 myapp.db

    You are now inside the SQLite REPL. Turn on human-readable output, then create a table and insert a row:

    sql
    .mode column
    .headers on

    CREATE TABLE users ( id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE, name TEXT, created_at TEXT DEFAULT (datetime('now')) );

    INSERT INTO users (email, name) VALUES ('[email protected]', 'Alice'), ('[email protected]', 'Bob');

    SELECT * FROM users;

    Expected output:

    text
    id  email              name   created_at
    --  -----------------  -----  -------------------
    1   [email protected]  Alice  2026-04-16 10:00:00
    2   [email protected]    Bob    2026-04-16 10:00:00

    Useful dot-commands inside the REPL:

    • .tables -- list all tables
    • .schema users -- show the CREATE TABLE statement
    • .indexes users -- list indexes on a table
    • .mode column / .mode box / .mode json -- change output format
    • .headers on -- include column names in output
    • .dump -- emit a SQL backup of the entire database
    • .quit -- exit
    Exit with .quit and try a one-off query from the shell:

    bash
    sqlite3 myapp.db "SELECT COUNT(*) FROM users;"

    sqlite-utils Power Tools

    sqlite-utils turns flat files into tables in one command. Create a CSV and import it:

    bash
    cat > people.csv <<'EOF'
    id,name,city
    1,Alice,London
    2,Bob,Berlin
    3,Chen,Taipei
    EOF

    sqlite-utils insert myapp.db people people.csv --csv sqlite-utils tables myapp.db --counts

    Expected output:

    text
    [{"table": "users", "count": 2},
     {"table": "people", "count": 3}]

    Other high-leverage commands:

    • sqlite-utils memory data.csv "SELECT city, COUNT(*) FROM t GROUP BY city" -- run SQL against a CSV without even creating a database file.
    • sqlite-utils extract myapp.db people city -- normalize a repeated column into its own lookup table with a foreign key.
    • sqlite-utils transform myapp.db users --add phone TEXT -- safely add columns, reorder, or change types.
    • sqlite-utils analyze-tables myapp.db -- summary stats for every column (distinct counts, nulls, most common values).

    Step 4: Production PRAGMAs (WAL and Friends)

    SQLite's default settings are conservative and optimized for safety on arbitrary hardware. For a server-side web app on a modern SSD, change four things and you will get 10-100x better throughput.

    Open the database and run:

    bash
    sqlite3 myapp.db
    sql
    PRAGMA journal_mode = WAL;
    PRAGMA synchronous = NORMAL;
    PRAGMA foreign_keys = ON;
    PRAGMA busy_timeout = 5000;

    What each one does:

    • journal_mode = WAL (Write-Ahead Logging) -- This is the single most important setting. WAL allows readers and one writer to work concurrently without blocking each other. Writes append to myapp.db-wal and are periodically checkpointed back into the main database. This setting is persistent -- once set, it stays set for that database file.
    • synchronous = NORMAL -- In WAL mode, NORMAL is safe (no corruption on OS crash) but skips a redundant fsync that FULL does. Expect a 2-5x write speedup with no meaningful durability loss on ext4 + journaling.
    • foreign_keys = ON -- Foreign key constraints are disabled by default (for backwards compatibility, astonishingly). You must enable them per-connection. Your application should run this on every connection open.
    • busy_timeout = 5000 -- When the writer lock is held by another connection, instead of immediately returning SQLITE_BUSY, wait up to 5 seconds for the lock. This makes the "database is locked" error nearly impossible to hit under normal load.
    Your application should issue PRAGMA foreign_keys=ON and PRAGMA busy_timeout=5000 on every connection (these are per-connection). journal_mode and synchronous persist.

    For a Python SQLAlchemy app, for example:

    python
    from sqlalchemy import event, create_engine

    engine = create_engine("sqlite:///myapp.db")

    @event.listens_for(engine, "connect") def set_sqlite_pragmas(conn, _): cur = conn.cursor() cur.execute("PRAGMA foreign_keys=ON") cur.execute("PRAGMA busy_timeout=5000") cur.close()

    After enabling WAL, you will see two extra files next to myapp.db:

    text
    myapp.db       -- main database
    myapp.db-wal   -- write-ahead log
    myapp.db-shm   -- shared-memory index

    All three must be kept together. Copy them as a unit, or use the online backup API (next section).

    Step 5: Live Backups

    The classic Unix instinct is cp myapp.db backup.db. Do not do this on a live database -- you may capture a half-written transaction and produce a corrupt backup.

    Use one of two safe methods instead.

    Method A: VACUUM INTO

    This is the simplest live backup. It writes a clean, compacted copy of the database to a new file:

    bash
    sqlite3 myapp.db "VACUUM INTO 'myapp-backup-$(date +%F).db';"

    This is transactionally consistent, works on a live database, and defragments in the process.

    Method B: .backup (Online Backup API)

    The .backup dot-command uses SQLite's online backup API, which copies page-by-page while the source database continues to accept writes:

    bash
    sqlite3 myapp.db ".backup 'myapp-backup.db'"

    Both methods are safe on a WAL-mode database. For a nightly cron, VACUUM INTO is usually the right choice.

    Optional: GUI Access with DB Browser for SQLite

    For ad-hoc exploration, the open-source DB Browser for SQLite provides a desktop GUI. Install it on your laptop (not the server) and copy backup files down via scp, or point it at a remote file via an SFTP mount. It is the same app internal teams at government agencies use for records exports.

    Step 6: Continuous Replication with Litestream

    A nightly file copy is nice, but real production needs near-zero RPO (recovery point objective). Litestream by Ben Johnson (now Fly.io) streams the SQLite WAL to an object store (S3, Backblaze B2, Azure, GCS, or even another local filesystem) in real time. If your VPS dies, you restore from S3 with at most a few seconds of data loss.

    Install the Litestream Binary

    Download the latest release for Linux amd64:

    bash
    curl -LO https://github.com/benbjohnson/litestream/releases/download/v0.3.13/litestream-v0.3.13-linux-amd64.deb
    sudo dpkg -i litestream-v0.3.13-linux-amd64.deb
    litestream version

    Expected output:

    text
    v0.3.13

    Configure Replication to S3

    Create /etc/litestream.yml:

    bash
    sudo tee /etc/litestream.yml > /dev/null <<'EOF'
    dbs:
      - path: /home/ubuntu/sqlite/myapp.db
        replicas:
          - type: s3
            bucket: my-litestream-backups
            path:   myapp
            region: us-east-1
            access-key-id:     YOUR_AWS_KEY
            secret-access-key: YOUR_AWS_SECRET
    EOF
    sudo chmod 600 /etc/litestream.yml

    Backblaze B2 and any S3-compatible store work too -- just add an endpoint: key. For B2:

    yaml
    - type: s3
            endpoint: https://s3.us-west-002.backblazeb2.com
            bucket:   my-bucket
            path:     myapp

    Run Litestream as a systemd Service

    The Debian package already installs litestream.service. Enable and start it:

    bash
    sudo systemctl enable --now litestream
    sudo systemctl status litestream

    You can confirm replication is live:

    bash
    sudo journalctl -u litestream -f

    Expected output:

    text
    level=INFO msg="litestream v0.3.13"
    level=INFO msg="initialized db" path=/home/ubuntu/sqlite/myapp.db
    level=INFO msg="replicating to: name=s3 bucket=my-litestream-backups ..."
    level=INFO msg="write snapshot" ...

    Restore from a Replica

    On a fresh server (or in disaster recovery):

    bash
    litestream restore -o /home/ubuntu/sqlite/myapp.db \
      s3://my-litestream-backups/myapp

    Litestream replays the WAL up to the most recent committed transaction. RPO in practice is under 10 seconds.

    Step 7: Explore Data with Datasette

    Datasette is a read-only web UI and JSON API for SQLite databases, also by Simon Willison. Point it at any .db file and you get instant browsable tables, facets, full-text search, graphs, and an OpenAPI-style JSON endpoint -- ideal for internal dashboards or publishing open data.

    Install it with pipx:

    bash
    pipx install datasette
    datasette serve ~/sqlite/myapp.db --host 0.0.0.0 --port 8001

    Browse to http://your-server-ip:8001 and explore. For production, run it behind Nginx with basic auth or put it on a private IP.

    Step 8: Install libSQL Server (sqld) for Turso-Compatible Access

    SQLite's one limitation for server apps is that the database lives inside one process on one machine. If you want multiple servers to query the same SQLite database -- or if you want your laptop dev app to connect to the same database your production API uses -- you need a networked SQLite. That is what libSQL solves.

    libSQL is an open-source, MIT-licensed fork of SQLite maintained by Turso. It adds HTTP and gRPC wire protocols, native replication, and embedded-replica clients. sqld is the daemon that speaks these protocols. You can run Turso managed cloud, or self-host sqld on your own VPS and get the same client APIs.

    Install sqld

    Turso publishes a one-line installer that drops sqld and the turso CLI into ~/.turso/bin:

    bash
    curl -sSfL https://get.tur.so/install.sh | bash
    source ~/.bashrc
    sqld --version

    Expected output:

    text
    sqld 0.24.32

    Run sqld Manually

    Pick a data directory and launch:

    bash
    mkdir -p ~/sqld-data
    sqld --http-listen-addr 0.0.0.0:8080 --db-path ~/sqld-data

    Expected output:

    text
    INFO Starting sqld
    INFO HTTP listening on 0.0.0.0:8080
    INFO Primary node ready

    sqld stores each logical database as a directory under --db-path. The HTTP endpoint implements the Hrana protocol (Turso's line protocol), plus a compatibility layer so any HTTP client can POST SQL:

    bash
    curl -s http://localhost:8080/v2/pipeline -H "Content-Type: application/json" -d '{
      "requests": [
        {"type": "execute", "stmt": {"sql": "CREATE TABLE kv (k TEXT PRIMARY KEY, v TEXT)"}},
        {"type": "execute", "stmt": {"sql": "INSERT INTO kv VALUES (?, ?)", "args": [{"type":"text","value":"hello"},{"type":"text","value":"world"}]}},
        {"type": "execute", "stmt": {"sql": "SELECT * FROM kv"}},
        {"type": "close"}
      ]
    }'

    Run sqld as a systemd Service

    For production, supervise it with systemd:

    bash
    sudo tee /etc/systemd/system/sqld.service > /dev/null <<'EOF'
    [Unit]
    Description=libSQL Server (sqld)
    After=network.target

    [Service] Type=simple User=ubuntu Environment="SQLD_HTTP_LISTEN_ADDR=0.0.0.0:8080" Environment="SQLD_DB_PATH=/home/ubuntu/sqld-data" ExecStart=/home/ubuntu/.turso/bin/sqld Restart=on-failure RestartSec=5

    [Install] WantedBy=multi-user.target EOF

    sudo systemctl daemon-reload sudo systemctl enable --now sqld sudo systemctl status sqld

    How libSQL Extends SQLite

    Compared to stock SQLite, libSQL adds:

    • HTTP + WebSocket + gRPC remote access -- Any client, any language, over the wire.
    • Native streaming replication -- sqld primary pushes a consistent frame log to read-replica sqld instances.
    • Embedded replicas -- A client library can open a local SQLite file that the library itself keeps in sync with a remote primary. Reads hit local disk in microseconds; writes are forwarded to the primary and acknowledged when durable. This gives you Postgres-read-replica semantics without running a separate replica server.
    • Still single-writer -- libSQL preserves SQLite's single-writer guarantee, which is what keeps semantics sane. Multi-node writes funnel through the primary.
    • 100% SQLite on-disk format compatibility -- You can sqlite3 a libSQL data file directly. There is no migration gap.

    Step 9: Secure sqld with TLS and JWT Auth

    sqld on port 8080 with no auth is fine for localhost. Expose it publicly and you need two things: JWT authentication and TLS via Nginx.

    Enable JWT Auth

    sqld accepts a JWT-signing public key and requires every request to present a bearer token signed by the corresponding private key. Generate a keypair:

    bash
    openssl genpkey -algorithm ED25519 -out sqld-private.pem
    openssl pkey -in sqld-private.pem -pubout -out sqld-public.pem

    Start sqld with the public key:

    bash
    sqld \
      --http-listen-addr 0.0.0.0:8080 \
      --db-path ~/sqld-data \
      --auth-jwt-key-file /home/ubuntu/sqld-public.pem

    Update the systemd unit accordingly. Your application signs a JWT with the private key (any standard JWT library works) and sends Authorization: Bearer <token> on every request.

    Reverse Proxy with TLS

    Put Nginx in front for TLS termination:

    bash
    sudo apt install -y nginx certbot python3-certbot-nginx
    bash
    sudo tee /etc/nginx/sites-available/sqld > /dev/null <<'EOF'
    server {
        listen 443 ssl http2;
        server_name db.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/db.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/db.yourdomain.com/privkey.pem;

    client_max_body_size 50m;

    location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;

    # Needed for Hrana WebSocket upgrade proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; } }

    server { listen 80; server_name db.yourdomain.com; return 301 https://$host$request_uri; } EOF

    sudo ln -s /etc/nginx/sites-available/sqld /etc/nginx/sites-enabled/ sudo certbot --nginx -d db.yourdomain.com sudo nginx -t && sudo systemctl reload nginx

    You can now connect clients to libsql://db.yourdomain.com with the JWT bearer token.

    Step 10: Embedded Replicas from Client Apps

    The killer feature of libSQL is the embedded replica: your application opens a local SQLite file that the libSQL client library keeps in sync with your sqld primary. Reads never leave the process. Writes are forwarded to the primary transparently.

    Python

    bash
    pip install libsql-experimental
    python
    import libsql_experimental as libsql

    conn = libsql.connect( "local-replica.db", sync_url="libsql://db.yourdomain.com", auth_token="YOUR_JWT", ) conn.sync() # pull latest from primary

    cur = conn.cursor() cur.execute("SELECT * FROM users") print(cur.fetchall())

    cur.execute("INSERT INTO users (email, name) VALUES (?, ?)", ("[email protected]", "Carol")) conn.commit() # write forwarded to primary

    Node.js / TypeScript

    bash
    npm install @libsql/client
    ts
    import { createClient } from "@libsql/client";

    const client = createClient({ url: "file:local-replica.db", syncUrl: "libsql://db.yourdomain.com", authToken: process.env.TURSO_AUTH_TOKEN!, });

    await client.sync(); const result = await client.execute("SELECT * FROM users"); console.log(result.rows);

    Official clients also exist for Rust (libsql crate), Go (github.com/tursodatabase/libsql-client-go), Dart/Flutter, and PHP.

    Self-Hosted sqld vs Turso Cloud

    Turso Cloud runs this exact same sqld binary on their infrastructure with edge replication across 35+ regions. For hobby projects it is free (500 databases, 9 GB storage). Self-hosting on your own CloudCore VPS costs a flat monthly fee and gives you full data residency, no quota limits, and zero network egress to a third party. The client APIs are identical -- you can switch between them by changing a URL.

    rqlite: A Distributed Alternative

    If your requirement is strongly-consistent multi-node writes (not just read replicas), look at rqlite. rqlite wraps SQLite in a Raft consensus group: every node can accept writes, they are replicated via Raft, and you get linearizable reads across a 3-or-5-node cluster.

    Quick install:

    bash
    curl -L https://github.com/rqlite/rqlite/releases/download/v8.26.7/rqlite-v8.26.7-linux-amd64.tar.gz | tar xz
    cd rqlite-v8.26.7-linux-amd64
    ./rqlited -node-id 1 ~/rqlite-data

    rqlite is the right answer when you need multi-writer SQLite with HA failover. libSQL is the right answer when you want single-writer SQLite with fast read replicas and embedded local caches. They are complementary, not competing.

    Troubleshooting

    ProblemCauseSolution
    Error: database is lockedWAL off, busy_timeout missing, or long-running write txnEnable WAL (PRAGMA journal_mode=WAL) and set PRAGMA busy_timeout=5000 on every connection. Shorten long transactions.
    Backup file is corrupt when copied while app is runningUsed cp instead of VACUUM INTO or .backupUse sqlite3 db.sqlite "VACUUM INTO 'backup.db';" or Litestream. Never cp a live SQLite file.
    Disk fills up unexpectedlymyapp.db-wal grew without checkpointingRun PRAGMA wal_checkpoint(TRUNCATE); or set PRAGMA wal_autocheckpoint=1000;. Shrink with VACUUM.
    SQLite is "slow" on cloud storageDatabase file is on NFS, EFS, or networked block storage with poor fsync semanticsSQLite requires correct fsync. Run on local NVMe. Never place a SQLite file on NFS -- corruption is guaranteed on concurrent access.
    "SSD lasts 6 months" / high wearWrite amplification from tiny transactions + synchronous=FULLSet synchronous=NORMAL, batch writes in transactions, and use WAL. Modern enterprise NVMe handles billions of writes.
    sqld won't start: "address in use"Another process on 8080sudo lsof -i :8080. Kill or change --http-listen-addr.
    sqld data directory from another version fails to openVersion mismatch after upgradeBack up first, then follow Turso release notes. Major versions occasionally require migration.
    Concurrent writes from multiple hostsTwo separate processes on different machines writing to the same fileNever do this. Put sqld in front of the file. Only sqld should write to --db-path.

    FAQ

    When is SQLite the wrong choice?

    Reach for Postgres or another client-server database when you need: (1) multiple machines writing to the same database with strong consistency, (2) OLAP workloads over hundreds of GB where ClickHouse or DuckDB would be a better fit, (3) advanced types like JSONB with indexing, PostGIS, or full-text search beyond SQLite's FTS5, (4) role-based access control enforced at the database level, or (5) true concurrent write throughput in the thousands-per-second range across multiple writers. For roughly 90% of web apps -- blogs, SaaS dashboards, internal tools, APIs with less than a few hundred writes/sec -- SQLite will outperform a networked Postgres on the metrics that matter (latency and ops cost).

    SQLite vs Postgres for a web app -- which should I pick?

    If your app is a single process (or a few processes) on one VPS and your write load is moderate, SQLite with WAL + Litestream will be faster, cheaper, and simpler than Postgres. A query that takes 2 ms over the network takes 20 microseconds in-process. If your app is multi-region, multi-writer, or needs Postgres-specific features (JSONB, PostGIS, pg_vector, row-level security), use Postgres. See our Postgres install guide for that path.

    Is libSQL fully compatible with SQLite?

    Yes. libSQL is a fork that tracks upstream SQLite closely and preserves the on-disk format. Every SQLite pragma, every SELECT, every index works. libSQL adds features (native replication, WebAssembly user-defined functions, vector search) on top, but never removes or breaks existing SQLite behavior. You can take a libSQL database file and open it with the stock sqlite3 CLI right now.

    rqlite vs libSQL -- which do I want?

    Pick libSQL if you want one primary writer with read replicas, embedded-replica client libraries, and HTTP/gRPC remote access. Pick rqlite if you want multi-node fault tolerance with automatic failover and Raft-backed strong consistency across the cluster. libSQL prioritizes SQLite's single-writer model and edge replicas; rqlite prioritizes cluster HA. Both are excellent; they solve different problems.

    How much does Turso Cloud cost vs self-hosting?

    Turso Cloud has a generous free tier (500 databases, 9 GB total storage, 1 billion row reads/month) and paid plans starting around USD 29/month. Self-hosting sqld on a CloudCore Starter VPS at EUR 7.99/month gives you unlimited databases, unlimited queries, unlimited storage (up to disk size), full data residency, and zero vendor lock-in. The right choice depends on whether you value Turso's global edge replication (pick Cloud) or flat-rate simplicity and sovereignty (pick self-hosted).

    Can I run SQLite in Docker?

    Yes -- but the database file should live on a mounted volume on local NVMe, not inside the container's ephemeral layer and never on a bind-mounted network volume. A common pattern is to run your application container with -v /opt/app/data:/data pointing at a VPS-local SSD directory. See our Docker install guide for setup.

    Next Steps

    Now that SQLite and libSQL are running, here are high-leverage next moves:

    • Set up nightly Litestream restore drills -- A backup you have never restored is not a backup. Schedule a monthly litestream restore into a scratch VPS and diff against production to prove your RPO.
    • Wire up Datasette publish -- Use datasette publish to turn any SQLite database into a deployable static site with an API. Great for internal dashboards and public open-data portals.
    • Add a libSQL read replica on a second VPS -- Run a second sqld instance in --replica mode pointed at your primary for geographically distributed reads.
    • Integrate full-text search with FTS5 -- Create an FTS5 virtual table and watch your search queries return in sub-millisecond latency without Elasticsearch.
    • Combine with Docker Compose -- Deploy sqld alongside your app, Litestream, and Datasette in a single compose file. See the Docker install guide.
    • Try vector search -- libSQL supports native vector indexes. Store OpenAI or local embeddings directly in SQLite and query with vector_top_k() -- no separate vector database required.

    Skip the Manual Install -- Get SQLite + libSQL Pre-Configured
    >
    Our CloudCore Starter VPS comes with SQLite tuned for WAL, sqld running as a systemd service, Litestream streaming to S3, and Nginx + TLS in front of the libSQL endpoint. Deploy in 60 seconds and start writing queries immediately.
    >
    - sqlite3 and sqlite-utils pre-installed
    - WAL + production PRAGMAs applied
    - sqld listening on a TLS-protected subdomain
    - Litestream configured with your S3/B2 bucket
    - Datasette optional for browsing data
    >
    Deploy Your SQLite VPS Now -- CloudCore Starter from EUR 7.99/month.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket