The mental model
SQLite + Litestream (see that tutorial) gives you one writer + asynchronous replicas (object storage). rqlite gives you a synchronously-replicated cluster: a write isn't acknowledged until a majority of nodes have it. Lose a node, the cluster keeps running. Lose the leader, a new one is elected in a few hundred milliseconds.
The trade: writes are slower (network round-trips to majority) but the data is genuinely consistent across nodes + survives node failures without manual restore.
Install
# Linux
RQVER=8.30.0
curl -L "https://github.com/rqlite/rqlite/releases/download/v${RQVER}/rqlite-v${RQVER}-linux-amd64.tar.gz" \
| tar -xz
sudo install rqlite-v${RQVER}-linux-amd64/rqlited /usr/local/bin/
sudo install rqlite-v${RQVER}-linux-amd64/rqlite /usr/local/bin/
rqlited -version
Single-node (for testing)
# Start node 1
mkdir -p ~/rqlite/node1
rqlited -node-id 1 \
-http-addr 127.0.0.1:4001 \
-raft-addr 127.0.0.1:4002 \
~/rqlite/node1
# In another terminal: connect with the CLI
rqlite -H 127.0.0.1 -p 4001
# > CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT);
# > INSERT INTO users (email) VALUES ('alice@example.com');
# > SELECT * FROM users;
3-node cluster
# Node 1 (the bootstrap)
rqlited -node-id 1 \
-http-addr node1:4001 \
-raft-addr node1:4002 \
/var/lib/rqlite/node1
# Node 2 (joins node 1)
rqlited -node-id 2 \
-http-addr node2:4001 \
-raft-addr node2:4002 \
-join http://node1:4001 \
/var/lib/rqlite/node2
# Node 3
rqlited -node-id 3 \
-http-addr node3:4001 \
-raft-addr node3:4002 \
-join http://node1:4001 \
/var/lib/rqlite/node3
# Status
rqlite -H node1 -p 4001 -e '.status'
Within seconds, Raft has elected a leader; every node has the same data. Writes can go to any node; non-leaders forward to the leader.
HTTP API
# Execute a write (POST)
curl -L -d '{
"statements": [
"CREATE TABLE foo (id INTEGER PRIMARY KEY, name TEXT)",
"INSERT INTO foo(name) VALUES(\"bar\")"
]
}' -H "Content-Type: application/json" \
http://node1:4001/db/execute
# Query
curl -G 'http://node1:4001/db/query' \
--data-urlencode 'q=SELECT * FROM foo'
# With strong consistency (always go to leader; slower but strictly consistent)
curl -G 'http://node1:4001/db/query?level=strong' \
--data-urlencode 'q=SELECT * FROM foo'
# With weak consistency (any node, possibly slightly stale; fast)
curl -G 'http://node1:4001/db/query?level=weak' --data-urlencode 'q=...'
Consistency levels
- none — serve from the local node's copy. Fast; can be stale by up to the Raft replication lag (typically milliseconds).
- weak — default. Confirm the node is still a leader / follower; serve from local copy.
- linearizable — route to current leader; strict linearizability.
- strong — route through Raft (highest cost, strongest guarantee).
Pick per-query based on freshness requirements. For "is this user logged in?" use none/weak; for "did this payment just commit?" use strong.
Language clients
# Python
pip install pyrqlite
import pyrqlite.dbapi2 as rqlite
conn = rqlite.connect(host='node1', port=4001)
cur = conn.cursor()
cur.execute('CREATE TABLE IF NOT EXISTS users(id INTEGER, email TEXT)')
cur.execute('INSERT INTO users VALUES(?, ?)', (1, 'amir@example.com'))
conn.commit()
cur.execute('SELECT * FROM users')
print(cur.fetchall())
conn.close()
Or use the standard SQLite-shaped driver for your language pointed at the HTTP endpoint; clients exist for Go, Python, Java, Node.js, Rust, .NET.
Auth + TLS
# Generate certs (or use step-ca; see /tutorials/step-ca-internal-certificate-authority.html)
# users.json with bcrypt-hashed passwords
[
{
"username": "alice",
"password": "$2y$10$...",
"perms": ["all"]
}
]
# Start with auth + TLS
rqlited \
-auth users.json \
-http-cert /etc/rqlite/server.crt \
-http-key /etc/rqlite/server.key \
-node-cert /etc/rqlite/server.crt \
-node-key /etc/rqlite/server.key \
-node-id 1 ...
Per-user permissions: all, execute, query, status, backup, load, join. Combined with TLS, gives you both confidentiality and authn/authz across the cluster.
Snapshots + backup
# Snapshot the entire DB (HTTP endpoint)
curl -L http://node1:4001/db/backup -o snapshot.db
# Restore: load a SQL dump or SQLite file
curl -X POST -H "Content-Type: application/octet-stream" --data-binary @snapshot.db \
http://node1:4001/db/load
The Raft cluster also takes periodic state snapshots automatically (configurable); old log entries get truncated, keeping disk usage bounded.
Multi-region: read replicas
rqlite supports non-voting "read-only" nodes that follow the Raft log but don't participate in elections. Useful for reading from a region far from the writer cluster:
# Start a read-only node
rqlited -node-id 4 \
-raft-non-voter \
-join http://primary-region-leader:4001 \
...
Stale-read consistency from the local region with low latency; writes still go to the primary region.
When rqlite shines
- Configuration / control-plane data — small-write-rate, high-read-rate, high-availability needed.
- Application metadata stores — user accounts, settings, audit logs.
- Stateful services that don't need Postgres's feature surface — queues, simple registries, feature flags.
- Edge / multi-region deployments — the same simple binary runs everywhere; non-voter replicas for read locality.
When it isn't
- High write throughput. Writes go through Raft consensus + a single leader; throughput is bounded. Postgres scales much further.
- Complex SQL features. rqlite is SQLite; no rich window functions, limited JSON support compared to Postgres, no extensions like pgvector or PostGIS.
- Transactions across multiple statements. rqlite supports batched statements as a transaction within one HTTP request, but cross-request transactions (BEGIN ... COMMIT across HTTP calls) aren't supported.
- Need to use a connection-pooled SQL driver pattern. Most language drivers do; rqlite's HTTP-shaped API is a different model.
rqlite vs alternatives
- SQLite + Litestream (see that tutorial) — one writer + S3 replication. Simpler; lower HA (manual recovery). Use when you don't need HA writes.
- LiteFS — SQLite-replication via FUSE; single-writer, multi-reader. Similar use case to rqlite read-only nodes.
- etcd / Consul — K/V stores with Raft; great for service discovery / config; not SQL.
- CockroachDB (see that tutorial) — distributed SQL with multi-leader writes; much more capable; much heavier ops. For "I'm building a real product with high write throughput," CockroachDB; for "I need HA SQLite," rqlite.
- Patroni + Postgres (see that tutorial) — Postgres feature surface + HA. Single-leader, manual failover via Patroni. More operational overhead than rqlite.
For "I want SQLite's simplicity + actual HA + replication-as-a-property-of-the-database, not bolted-on," rqlite is the right tool.