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.