Back Up and Replicate a Django SQLite Database With Litestream

Use Litestream to continuously replicate a Django SQLite database to S3-compatible storage: enable WAL mode, run Litestream as a sidecar or systemd service, restore on startup in containers, test point-in-time recovery, and know the limits of single-writer SQLite.

Problem statement

SQLite is a reasonable production database for many small Django apps: fast, no separate server, one file. The weakness is durability. If the server or volume is lost, so is everything since the last backup, and copying the file while the app writes can produce a corrupt backup. Litestream solves this by continuously streaming SQLite's write-ahead log to object storage, giving you backups seconds behind live data and point-in-time restore.

Litestream for DjangoDjango writesSQLite in WAL modeLitestreamtails the WALObject storageS3, R2, B2Restoreany point in timeLitestream ships WAL changes continuously, so backups lag seconds, not hours.
  1. Enable WAL mode for the Django SQLite database (Django 5.1+ supports an init_command option; otherwise run PRAGMA journal_mode=WAL once).
  2. Install Litestream and configure the database path and an S3-compatible replica in /etc/litestream.yml.
  3. Run Litestream alongside Django: as a systemd service, or with litestream replicate -exec "gunicorn ..." in a container.
  4. In containers with ephemeral disks, run litestream restore -if-replica-exists before starting the app.
  5. Test restores regularly, and keep exactly one writer process on one server.

Step-by-step solution

1. Configure SQLite for production

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": "/data/db.sqlite3",
        "OPTIONS": {
            "transaction_mode": "IMMEDIATE",
            "timeout": 5,
            "init_command": "PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA busy_timeout=5000;",
        },
    }
}

transaction_mode and init_command options were added in Django 5.1. WAL mode lets readers proceed while one writer writes, and Litestream requires it. Keep the database in a directory that the app user can write to.

2. Configure Litestream

# /etc/litestream.yml
dbs:
  - path: /data/db.sqlite3
    replicas:
      - type: s3
        bucket: shop-sqlite-backups
        path: prod/db
        endpoint: https://<account>.r2.cloudflarestorage.com
        region: auto
        retention: 168h
        sync-interval: 1s

Credentials come from LITESTREAM_ACCESS_KEY_ID and LITESTREAM_SECRET_ACCESS_KEY environment variables, or an IAM role on AWS. Litestream periodically writes snapshots and continuously uploads WAL segments; retention controls how far back you can restore.

worst-case loss after a server failure (illustrative)nightly file copy24 hhourly .backup60 minLitestreamseconds0 min720 min1440 minContinuous WAL shipping shrinks the loss window to secondsdjango-deployment.com
Restore speed matters too; test how long a full restore takes.

3. Run Litestream with the app

On a VM, install the package and enable its service:

sudo systemctl enable --now litestream

In a container, let Litestream supervise Gunicorn so both start and stop together:

#!/bin/sh
set -e
litestream restore -if-db-not-exists -if-replica-exists /data/db.sqlite3
python manage.py migrate --noinput
exec litestream replicate -exec "gunicorn config.wsgi --bind 0.0.0.0:8000 --workers 2"

If the container's disk is ephemeral (Fly.io Machines without volumes, Cloud Run), the restore step recreates the database from the replica on every start.

4. Restore and test

litestream restore -o /tmp/restore.sqlite3 /data/db.sqlite3
litestream restore -o /tmp/restore.sqlite3 -timestamp 2026-10-03T08:00:00Z /data/db.sqlite3
sqlite3 /tmp/restore.sqlite3 "PRAGMA integrity_check;"

Schedule a restore test and compare row counts with production. A backup that has never been restored is not proven.

5. Respect SQLite's single-writer model

Litestream does not make SQLite multi-server. Exactly one instance must write to the database file, on one machine. Run one container or VM, not several replicas; scaling out needs PostgreSQL or a different replication approach. Running two Litestream processes against the same replica path corrupts the backup history.

entrypoint order1restore if replica existsonly when DB missing2manage.py migrateschema up to date3litestream replicate -execstarts Gunicorn4WAL streamed to storageevery second5Shutdown flushes WALthen exitsLitestream wraps the app so replication runs exactly as long as Djangodjango-deployment.com
One container, one writer: never scale this setup beyond a single instance.

Explanation

In WAL mode, SQLite appends changes to a -wal file and periodically checkpoints them into the main database. Litestream holds a read transaction to control checkpointing, copies new WAL frames to object storage, and writes snapshots of the full database at intervals. Restoring means downloading the latest snapshot before the target time and replaying WAL segments up to it. Because it reads the WAL rather than copying a live file, backups are consistent without stopping the app.

Edge cases / notes

  • Media files. Litestream only covers the SQLite file. Store uploads in object storage or back them up separately.
  • Checkpoints. Avoid running VACUUM or manual checkpoints from other tools while Litestream runs; let Litestream manage checkpointing.
  • Version changes. Litestream has evolved its replica format across major versions; check upgrade notes before upgrading production.

Next steps

FAQ

Does Litestream let me run several Django servers on SQLite?

No. It provides backups and restore, not multi-writer replication. Keep one writer.

Do I need WAL mode?

Yes. Litestream works by reading SQLite's write-ahead log.

How much data can I lose with Litestream?

Typically only the last second or so of writes, depending on the sync interval.

Where can Litestream store backups?

S3 and S3-compatible services such as Cloudflare R2, Backblaze B2, and MinIO, plus other supported backends.

Keep going