SQLite backup to S3 with Litestream: set it up, then check which restore points actually exist
Set up continuous SQLite backup to S3 or a compatible store with Litestream v0.5, then learn why a replicated transaction can still fail to restore and which settings widen the window.
Akash Mohapatra · · Updated · 9 min read · 19 views
A SQLite backup to S3 with Litestream takes one config file and one background process. By default it uploads new changes about once a second, so a crash costs you whatever was written since the last sync. The catch comes at restore time: you can only land on the boundaries of files that still exist in your bucket, and those get coarser as retention runs.
We read the Litestream documentation for the v0.5.x line on October 25, 2026. Defaults and flags change between releases, so check the version you run against the pages linked below. This is a setup guide and a restore checklist, not a benchmark.
What Litestream copies, and why it is a backup rather than a cluster
SQLite in WAL mode writes each change to a separate write-ahead log file first and copies those pages back into the main database later, in a step called a checkpoint. Litestream's how-it-works page says it takes over that checkpointing: it holds a long-running read transaction so no other process can restart the WAL, reads the new WAL pages, and calls checkpoints itself when needed.
The new pages are packaged into LTX files, each stamped with the next transaction ID (TXID), staged in a hidden directory next to your database and uploaded to the replica. Replication is asynchronous, and each database replicates to a single destination. That makes it disaster recovery for one server, similar in spirit to log shipping on Postgres or MySQL, not a way to serve writes from several machines.
If you need that second thing, the tool is different. Litestream's own alternatives page points to LiteFS for high availability and low global latency, and to the Raft-based rqlite and dqlite for higher durability at the cost of write throughput, since every write commits to a quorum. Philip O'Toole, in an October 23, 2026 retrospective on rqlite, says rqlite is a CP system whose partitioned minority refuses writes, and that Litestream gives disaster recovery for a single node without consensus. His summary: Litestream asks what happens if the machine dies; rqlite asks what happens if it dies and you cannot wait to restore.
Set up a SQLite backup to S3 in six steps
- Create a bucket and an access key that can reach only that bucket. Any S3-compatible store works; the provider table below lists the ones Litestream detects automatically.
- Set three pragmas when your application opens its connection. Per Litestream's tips page, set busy_timeout to 5000 so your writes wait out Litestream's short checkpoint locks instead of erroring, and turn foreign_keys on for every connection. Optionally set synchronous to NORMAL, which the page says relaxes fsync calls in WAL mode without risking corruption. Litestream switches the database to WAL itself if it is not already.
- Write the config file. The configuration reference says the default path is /etc/litestream.yml, a dbs list takes a path and a replica, and an S3 replica can be a single url of the form s3://BUCKETNAME/PATHNAME. Credentials can come from LITESTREAM_ACCESS_KEY_ID and LITESTREAM_SECRET_ACCESS_KEY, or the AWS variables.
- Start the daemon with the replicate command and keep it running under a service manager. The docs list guides for systemd, Kubernetes, Windows and Docker.
- Look in the bucket. Replica files are organized by compaction level under an ltx/ prefix, per the how-it-works page. If the bucket stays empty after a few writes, check the daemon's output, the bucket name and the endpoint first.
- Restore into a scratch path and open the result. The restore reference takes the replica URL and an -o output path, has an -integrity-check flag with none, quick and full modes, and refuses to overwrite an existing non-empty file unless you pass -force (added in v0.5.12).
Do step 6 before you trust anything. In our view, a backup you have never restored is a hypothesis. We'd repeat the restore after every Litestream upgrade and every change to the config file, because the restore points you get depend on settings that live in that file.
Which S3-compatible stores Litestream detects
Since v0.5.0, the S3-compatible services guide says Litestream recognizes several providers by their endpoint and sets the right options for you. As of October 2026, Upfynd lists three of them in its Databases & Infrastructure category.
| Provider | Endpoint pattern Litestream detects | Catch from the docs |
|---|---|---|
| Backblaze (B2) | Hostnames ending in backblazeb2.com | Detected automatically from v0.5.0 |
| DigitalOcean Spaces | Hostnames ending in digitaloceanspaces.com | Detected automatically from v0.5.0 |
| Hetzner Object Storage | Hostnames ending in your-objectstorage.com | Detected from v0.5.9, and only when the endpoint is in the replica url field |
| Cloudflare R2 | Hostnames ending in r2.cloudflarestorage.com | Litestream also sets concurrency to 2 |
| MinIO | A custom host:port that is not a known provider | Remote MinIO needs the endpoint parameter in the config file |
Why a replicated transaction can fail to restore
Litestream replays whole LTX files and never part of one, according to the restore reference. If you ask for a transaction ID that falls in the middle of a file, that file is skipped, and the restore fails with "no matching backup files available" even though the transaction was replicated.
The docs show it with a small example. With files covering TXIDs 2 to 3 and 4 to 5, a restore to TXID 5 succeeds and a restore to TXID 4 fails. The same page warns that the error does not distinguish a transaction that was never replicated from one that is only reachable at a coarser granularity, so list what exists before you conclude data is missing.
To plan a restore, run the ltx command with -level all to list the files in the replica, then run restore with -dry-run (added in v0.5.12) to print the snapshot and the run of files it would replay without writing anything. A listed max_txid is a candidate, not a guarantee: the page notes that it still fails if retention has removed the snapshot beneath it. Two details trip people up. -txid is inclusive, while -timestamp is exclusive, so a file created at exactly that instant is skipped. And each restore needs its own output path, because the existing-file check runs first.
How the restore window narrows
The how-it-works page describes three stages, and the defaults come from the configuration reference.
| Age of the point you want | Finest restore point | Default that sets it |
|---|---|---|
| Recent, while level-0 files are kept | Roughly one per sync interval | sync-interval of 1s; level-0 files kept 5 minutes after compaction |
| Older, after level-0 files expire | Level-1 file boundaries | Level 1 is compacted every 30 seconds; levels 2 and 3 every 5 minutes and 1 hour |
| Older than the snapshot retention | Gone | Snapshots every 24 hours, kept 24 hours |
The page adds that a restore point available a few minutes ago can become permanently unreachable, and that retention always keeps at least one snapshot.
Keeping restore points longer
Two settings widen the window, and both trade storage cost for finer endpoints. Raise l0-retention to keep per-sync endpoints longer; the docs reject a value of 0 and give 8760h, one year, as an example of a long setting. Or set retention enabled to false (added in v0.5.8) so Litestream stops deleting anything from remote storage and your bucket's lifecycle rules decide what ages out. The configuration reference notes a side benefit: the credentials then no longer need DeleteObject permission, which limits the damage if they leak.
The tips page suggests lowering snapshot interval to 1h for write-heavy databases, because a restore replays every file since the last snapshot. In our view, most small apps should start there, keep the 24-hour retention, and raise snapshot retention only to match how far back a bad migration could go unnoticed.
What the sync interval costs
The configuration reference says the sync interval mainly drives PUT request costs, not storage costs: Litestream uploads only when the database changes, and a shorter interval means more requests while it does. The configuration reference works an example using S3's quoted request price of $0.000005: with constant writes, a 1-second interval is about 2,592,000 requests a month, or $12.96; 10 seconds is about $1.30; one minute is about $0.22.
Those are Litestream's illustrative figures, not a quote. Check your provider's current request pricing. The tips page states the trade plainly: at the default of one sync per second, a catastrophic crash loses the unsynced window. When Litestream gets a normal shutdown signal it tries to sync outstanding changes first, and the page says synchronous replication is on the roadmap but not implemented.
Five ways a Litestream backup quietly breaks
- Two apps, one bucket path. The tips page says multiple applications replicating into the same bucket and path can leave you unable to restore. Give every database its own path.
- Recreating a database. If you delete and recreate a SQLite file, remove the -shm and -wal files as well, then run litestream reset on the path, because Litestream does not track deletions.
- Heavy write load. With tens of thousands of small transactions per second, the app's own checkpoint can slip between Litestream's and force a fresh full snapshot. The docs recommend PRAGMA wal_autocheckpoint = 0 for that case and note that v0.5.0 reduced the need.
- Client-side encryption. Age encryption is not supported in v0.5.x. Since v0.5.1 a replica with an age block makes the daemon exit at startup, per the configuration reference, though server-side SSE-C and SSE-KMS remain available for S3 replicas. Upgrading from v0.3 with Age enabled needs the migration guide first.
- Auto-recover. It is off by default. Turned on, Litestream discards local tracking state on LTX errors, and you lose point-in-time recovery to times before the reset.
The config reference also lists a validation block that checks replica files for gaps, overlaps and sort order. It is disabled by default, and we'd turn it on.
When a nightly copy is enough, and when to use something else
Litestream's alternatives page says its approach can be overkill for a small database without high durability needs, and that daily or hourly backups, even alongside Litestream as a fallback, may be enough. SQLite's own VACUUM INTO command writes a consistent, minimal copy of a live database to a new file, which must not exist or must be empty. The page warns that an unplanned shutdown or power loss mid-run can leave an incomplete, corrupt output file, so check the copy before you upload it. Run it from cron, upload the file, and put the job on a heartbeat monitor: a scheduled backup that stops running fails silently. Healthchecks.io is one listed option, and our guide to cron job monitoring covers the setup.
We'd skip Litestream if you need several nodes accepting writes or automatic failover. The alternatives page names Raft-based rqlite and dqlite for stronger durability, and notes that Postgres and MySQL offer synchronous replication at the price of more operational work. Browse the 247 products Upfynd lists under Databases & Infrastructure and the 211 under Open Source as of October 2026. If you ship Litestream in a container, Docker is listed too.
Your next step takes ten minutes: run the replicate command against a copy of your database, write a few rows, restore to a scratch path with -integrity-check full, and run restore -dry-run against an older TXID to see how your own retention settings behave.
Frequently asked questions
How do I back up a SQLite database to S3 continuously?
Run Litestream as a background process next to your app. Point a config file at the database and an S3 URL, start the replicate command, and it uploads new write-ahead log pages about every second. Test the backup by restoring to a scratch path with the restore command.
How much data can Litestream lose?
By default Litestream syncs to an S3 replica every second, so a catastrophic crash can lose the changes written since the last sync. Its docs say synchronous replication is on the roadmap but not implemented. On a normal shutdown signal it tries to sync outstanding changes first.
Can Litestream restore to any moment in time?
No. It replays whole files, so a restore can only land on the boundary of a file still in the replica. Boundaries are finest while level-0 files are kept, five minutes by default, then coarser, and snapshots older than the 24-hour default retention are pruned.
Does Litestream work with Backblaze B2, Hetzner or other S3-compatible storage?
Yes. Since v0.5.0 it detects Backblaze B2, DigitalOcean Spaces, Cloudflare R2, MinIO and others from the endpoint and sets the needed options. Hetzner Object Storage is detected from v0.5.9, and only when the endpoint is written in the replica url field.
Is Litestream the same as rqlite or LiteFS?
No. Litestream streams one writer's database to object storage for disaster recovery. LiteFS targets high availability and low global latency, and rqlite uses Raft consensus so every write reaches a quorum, trading write throughput for durability. Pick by whether you need recovery or a running cluster.
Does Litestream support encryption?
Not client-side encryption in v0.5.x: Age support was removed, and since v0.5.1 a replica with an age block makes the daemon exit at startup. Server-side options remain for S3 replicas, namely SSE-C and SSE-KMS, according to the configuration reference. Check the docs for your version.
Products in this article
BackblazeRemote backup and cloud storage that saves your data to a secure datacenter
DigitalOceanCloud servers, managed databases and AI infrastructure for developers
DockerContainer platform to build, share and run apps in isolated environments
Healthchecks.ioCron job monitoring that alerts you when scheduled tasks stop running
HetznerDedicated servers, cloud hosting and S3-compatible object storage
About the author
Akash Mohapatra
Director & Co-founder