This is the multi-page printable view of this section. .
SQLite + Litestream
pg sqlite manages SQLite databases directly on a Linux host and can replicate
their WAL changes to S3-compatible object storage with
Litestream. SQLite runs as a normal host process — there
is no container, no Go SQLite driver and no cgo. The sqlite3 CLI executes all
SQL; Litestream runs as one detached process per database.
Replication is asynchronous (roughly one second of RPO with the default sync interval). It is continuous backup, not high availability: there is still a single writer, and a failover means restoring a backup somewhere else.
Only Linux is supported.
How it works
- Every managed database has an entry in
pg.yamland a per-name state directory under<base>/sqlite/<name>/. pg sqlite createenablesjournal_mode=WAL, which is persistent in the database header.pg sqlite replication enablerenders a dedicatedlitestream.yml(mode0600) and startslitestream replicatein the background. Litestream tails the WAL and uploads LTX objects to the configured bucket.- S3 credentials are written only to that
litestream.yml. They are never stored inpg.yamland are never printed by anypg sqlitecommand.
Install
The command is idempotent and installs two host binaries:
sqlite3from the distribution package manager if it is not already onPATH(apt-get, dnf, yum, pacman, zypper or apk). Only this step may need root; pgcli uses passwordlesssudo -nand fails with a clear message instead of prompting for a password.- Litestream
0.5.17, downloaded from GitHub and verified against the published SHA256 checksum. It goes to/usr/local/binwhen writable, otherwise to~/.local/bin.
Useful flags:
Litestream 0.3.x is not compatible with the generated configuration; the
installer ignores older binaries on PATH and installs 0.5.x.
Create and manage databases
create never truncates an existing file: pointing it at an existing SQLite
database adopts the file, enables WAL and leaves the contents untouched.
--path must be absolute and only moves the database file — configuration,
logs, pid and local Litestream state always stay under
<base>/sqlite/<name>/.
info reads journal_mode, page size/count and freelist, then runs
PRAGMA quick_check by default. Use --integrity full for a complete
integrity_check, or --integrity none to skip it on large databases.
remove unregisters the database and stops replication but leaves the database
file in place. Add --clean-data to delete the database, WAL sidecars, local
Litestream state and logs (interactive confirmation; --force skips it).
Remote S3 backups are never deleted automatically.
Enable S3 replication
The bucket must already exist — Litestream does not create buckets. Create it
with mc mb, the cloud console, or another S3 tool first.
Local MinIO for testing
pgcli can host the object store itself with the MinIO addon:
If port 9000 was already taken, use the endpoint reported by
pg addon list instead. Then point replication at the local store:
Any other MinIO or S3-compatible store uses the same flags with its own endpoint and credentials:
AWS S3 with the default credential chain (no keys on the command line):
| Flag | Default | Notes |
|---|---|---|
--s3-bucket |
required | Must already exist |
--s3-endpoint |
AWS default | Complete http(s)://host[:port], no path or credentials |
--s3-path |
sqlite/<name> |
Object key prefix; a leading / is stripped |
--s3-region |
us-east-1 |
|
--s3-access-key / --s3-secret-key |
default chain | Provide both or neither |
--s3-uri-style |
path |
path or virtual-host |
--s3-ca-file |
none | Passed to Litestream as SSL_CERT_FILE for private CAs |
--s3-skip-verify |
false | Disable TLS verification (self-signed endpoints) |
--sync-interval |
1s |
WAL upload interval |
--snapshot-interval |
24h |
Full snapshot interval |
--retention |
24h |
Snapshot retention |
--no-start |
false | Write configuration without starting Litestream |
--force |
false | Replace an existing configuration |
enable verifies that the database is in WAL mode, resolves a 0.5.x
Litestream binary (or uses --litestream-binary PATH), writes litestream.yml
with mode 0600, clears stale local Litestream state, starts the process and
checks that it survives startup. If it exits immediately, the last log line is
printed and the command fails.
Running enable again with identical non-secret settings while replication is
running is a no-op. Different settings are rejected unless --force is given;
--force stops the running process, rewrites the configuration, resets local
state and restarts.
Where the credentials live
| Path | Contents | Mode |
|---|---|---|
<base>/pg.yaml |
name, path, binary/version, endpoint, bucket, prefix, region, URI style, CA file, skip-verify — no keys | 0644 |
<base>/sqlite/<name>/<name>.db |
database file | app default |
<base>/sqlite/<name>/litestream.yml |
full S3 configuration including access/secret keys | 0600 |
<base>/sqlite/<name>/litestream.log |
process log | 0644 |
<base>/sqlite/<name>/litestream.pid |
running pid | 0644 |
<base>/sqlite/<name>/.<db>-litestream/ |
local LTX state | app default |
Back up or copy litestream.yml with the same care as any credential file.
replication disable removes it, so credentials must be supplied again on the
next enable.
Operate replication
status combines the process state with litestream status -json (local
transaction id and WAL size) and shows the newest error line from the log.
If the status query fails, the command still reports process state and exits
successfully.
Check remote backups
pg mc wraps the bundled MinIO client; its flags must come after --. Using
the alias created in the Local MinIO example, list what Litestream has uploaded
under the configured --s3-path:
A populated prefix looks like this:
0000/ holds per-transaction WAL frames; higher-numbered levels (0001/,
0009/, …) are merged and compacted files. An empty prefix right after
enable only means no write has been synced yet — insert a row, wait at least
one --sync-interval (default 1s), then list again. The local transaction id
shown by replication status should match the highest txid in these names.
disable keeps the database, local LTX state and logs, but removes the
replication block from pg.yaml and deletes litestream.yml. The remote
backups remain untouched.
Auto-start on boot
Litestream runs as a detached host process, so it does not survive a reboot on its own. Register the database with the pgcli boot service:
--name is required (SQLite has no default database name). Replication must
already be enabled and its litestream.yml must still exist — the toggle checks
both and fails with the usual re-enable hint otherwise. At boot,
pg start --autostart restarts Litestream after the object-storage addons, so
a same-host MinIO/silo/rustfs/garage/SeaweedFS backend is listening first. The
boot path is start-only: it never rewrites litestream.yml and never touches
the database file.
pg sqlite replication disable clears this flag automatically. See
Auto-start on Boot for the systemd/launchd service details.
Restore
Litestream can restore the latest state or a point in time. Two modes are available:
--timestamp and --txid are mutually exclusive; omitting both restores the
latest state. --timestamp is an absolute instant: write it in UTC with the
Z suffix (for example 2026-01-01T12:00:00Z) so the restore point does not
depend on the host timezone. --target cannot be combined with --restart,
and an existing target requires --force, which first moves the old target and
its -wal/-shm files into <target>.pre-restore-<UTC>/.
Point-in-time recovery walkthrough
Capture a transaction boundary before making the change you want to undo:
LTX object names are <min-txid>-<max-txid>.ltx. A restore by --txid includes
the state up to and including that transaction. Record the txid (or an RFC3339
UTC timestamp taken safely before the change), then restore a copy and verify
it:
The timestamp form follows the same flow:
Use --dry-run first when unsure: it prints the transaction range that would
be used and writes nothing. Restoring to --target does not stop replication,
so traffic can continue while you inspect the copy. As a sanity check, restore
the latest state (no --timestamp/--txid) and confirm that newer rows are
present; only then promote the verified copy with an in-place restore.
In-place restore
Without --target, the configured database is replaced:
The sequence is deliberately conservative:
- Litestream is stopped (a final sync runs) and other applications must have been stopped manually first.
- The current database and its WAL sidecars are moved to
<db>.pre-restore-<UTC>/(confirmation unless--force). - The backup is restored into a temporary directory next to the database, so a failed restore never touches the preserved copy.
- The restored database is checked and switched to WAL if necessary, then moved into place with an atomic rename on the same filesystem.
- Local Litestream state is reset. If reset fails, the stale state directory is removed.
- Replication stays stopped by default.
--restartstarts it again.
Restarting replication into the same --s3-path after a restore can mix the
restored timeline with the old remote data. Prefer re-enabling with a new
--s3-path, or make sure you understand the old timeline before using
--restart.
If the database file is lost entirely, in-place restore works without
--force; the per-database litestream.yml must still exist.
WAL and application settings
Only PRAGMA journal_mode=WAL is persisted in the database file by the CLI.
Tune the following per connection from the application:
Keep in mind:
- SQLite in WAL mode requires a local filesystem. Do not put the database on NFS or SMB.
- There is still one writer at a time. Long write transactions block other writers; long read transactions prevent WAL checkpointing and can make the WAL grow without bound.
- Litestream replicates the WAL, not an application-consistent snapshot. Use transactions for multi-statement invariants.
Troubleshooting
litestream exited during startup — run
pg sqlite replication logs <name> -n 50. The usual causes are a missing
bucket, wrong credentials or an endpoint that is not reachable from the host.
no Litestream 0.5.x binary found — run pg sqlite install. A 0.3.x
binary on PATH is reported but not used.
TLS errors against MinIO/RustFS — use the full http://host:port endpoint
for plain HTTP, or --s3-ca-file / --s3-skip-verify for self-signed HTTPS.
database ... uses journal_mode=delete — the database was not created by
pg sqlite create, or another tool changed the mode. Use
PRAGMA journal_mode=WAL; with no other connections open, then retry enable.
litestream.yml not found — start, status and restore require the
configuration written by enable; re-run enable (credentials are not stored
in pg.yaml).
Limits
- Linux only; the macOS host-binary paths are not implemented.
- Litestream is asynchronous backup, not synchronous replication or HA. The
default RPO is about one second, and a final sync on
stop/disablecan take up to 30 seconds. - One Litestream process and one WAL writer per database.
--clean-dataand in-place restore are destructive; they prompt by default, and the preserved.pre-restore-*directories are left for manual cleanup.