rqlite
Overview
rqlite is a lightweight, open source distributed relational database that uses SQLite as its storage engine and the Raft consensus protocol to replicate it. This guide covers deploying and using rqlite 10.6.4 on Ubuntu 24.04 LTS from SecureAnyCloud on Microsoft Azure.
You work with rqlite over a simple HTTP API (SQL in, JSON out), through its built-in web console or with the rqlite shell. It runs well as a single node, as this image ships it, and grows into a fault-tolerant cluster when you need one.
The version is rqlite 10.6.4, a security release. It fixes GHSA-f4f3-px5g-gm4x (critical: an unauthorized request on the Raft port could download a backup of the database). It also includes the fix for GHSA-j62w-8865-2h7x (high: ATTACH DATABASE through the query endpoint) from 10.6.3. Earlier versions of this image shipped rqlite 10.2.7, which both advisories affect.
What you get:
- No default login, no anonymous access. Started without an authentication file, rqlite has no authentication at all. This image always starts it with one. First boot creates the only rqlite user,
admin, with a 32-character password generated on your VM, and nothing is granted to anonymous requests. Every API path and the web console answer an unauthenticated or wrong-password request with401. The password is written to the root-only file/root/rqlite-credentials.txt, never to a command line, a log or the journal. - HTTPS only, on localhost. The HTTP API and the web console are served over TLS with a certificate generated on your VM at first boot. There is no plaintext listener. Both the API (
127.0.0.1:4001) and Raft (127.0.0.1:4002) listen on the loopback address only, so from the network only SSH (22) is reachable. You reach rqlite through an SSH tunnel (Step 8), or open it to your own network when you choose to (Step 10). - Built from source and scanned. rqlite 10.6.4 is compiled on the image from the unmodified upstream source at the
v10.6.4tag with Go 1.27.3, with every dependency exactly as upstream pins it. It is checked withgovulncheckat build time, which finds no known vulnerabilities in the code it links. The build provenance, the scan report and the license of every linked component ship on the image. - Hardened service.
rqliteruns as the unprivileged rqlite user with no capabilities, a read-only view of the system and no access to/rootor the Azure agent's files. It and its first boot service are blocked from the Azure Instance Metadata Service and the WireServer, because rqlite's SQL engine, change-data-capture and backup clients can be pointed at an address. The Azure Linux Agent and cloud-init keep their access. A start-up check refuses to start rqlite before first boot, without its data disk or TLS, or with an authentication file that would allow anonymous access. The automatic firmware-metadata refresh in fwupd is switched off, because Azure VMs have no firmware it can update. - Your data on its own disk. The data directory (
/var/lib/rqlite, holding the SQLite database, the Raft log and snapshots) is on a dedicated 32 GiB Azure data disk that you can grow or snapshot on its own. The image ships it empty, so each VM starts its own new cluster. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: rqlite 10.6.4 (rqlited, the server, runs as rqlited.service; rqlite, the shell), the helpers rqlite-query (runs SQL and API calls with your credentials) and rqlite-rotate-password (sets a new password), Ubuntu 24.04 LTS, cloud-init and the Azure Linux Agent. The database is empty, ready for your tables.
Default user: the administrator you choose at deployment (azureuser in the examples). Architecture: x64, Gen2. Recommended size: Standard_B2s (2 vCPU, 4 GiB) or larger.
What this is not
This is a single-node rqlite. A single node is a complete database, and reads and writes go through Raft and SQLite as usual. It is not fault tolerant, so protect it with disk snapshots and backups (Step 14), and see Step 13 to grow it into a cluster.
rqlite replicates every write to a majority of nodes, which suits read-heavy workloads and moderate write rates. It is not a drop-in replacement for a large OLTP server.
Prerequisites
- An Azure subscription, an SSH key pair, and a virtual network and subnet in your region
- A network security group (NSG) that allows TCP 22 (SSH) from your admin network. rqlite listens on the loopback address only, so no database port needs to be open. You reach it through an SSH tunnel.
Deploy from the Azure Marketplace
Sign in to the Azure portal, choose Create a resource, search the Marketplace for rqlite by SecureAnyCloud and select Create.
On Basics, pick your subscription, resource group, region and size (Standard_B2s or larger). Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) only.
The Disks tab shows the image's 32 GiB data disk (LUN 0) for /var/lib/rqlite. Make it as large as your data needs. Then choose Review + create, then Create. First boot takes a few seconds once the VM is running.
Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deployment)
az vm image terms accept --urn sac1647283583153:rqlite-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name rqlite \
--image sac1647283583153:rqlite-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standardaz vm create opens SSH (22) and nothing else. To make the data disk larger, resize it while the VM is deallocated. Then grow the file system once the VM is running again:
az vm deallocate --resource-group <your-rg> --name rqlite
az disk update --resource-group <your-rg> --name <data-disk-name> --size-gb 128
az vm start --resource-group <your-rg> --name rqlite
sudo resize2fs "$(findmnt -no SOURCE /var/lib/rqlite)"Connect to your VM
Connect over SSH as the administrator you chose at deployment:
ssh azureuser@<vm-public-ip>Check the services
Confirm that the first boot service and rqlite are both active:
systemctl is-active rqlite-firstboot.service rqlited.serviceactive activerqlite listens on the loopback address only, so from the network only SSH is reachable. Check the listeners:
sudo ss -Htln | awk '{print $4}' | sort -V0.0.0.0:22 127.0.0.1:4001 127.0.0.1:4002 127.0.0.53%lo:53 127.0.0.54:53 [::]:22Port 4001 is the HTTPS API and web console, and port 4002 is Raft. The port 53 lines are Ubuntu's local DNS resolver, also loopback only. The data directory lives on the dedicated data disk:
lsblk -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINTS /dev/disk/azure/scsi1/lun0NAME SIZE FSTYPE LABEL MOUNTPOINTS sda 32G ext4 rqlitedata /var/lib/rqliteThe kernel names disks in the order it finds them, so the data disk's device name (sda here, sdc on other VMs) can differ from VM to VM. /dev/disk/azure/scsi1/lun0 always points at it, and /etc/fstab mounts it by its file system UUID.
Get your password
Your connection details are in /root/rqlite-credentials.txt. Show them:
sudo grep -E '^RQLITE_(USER|URL|CONSOLE_URL|TLS_CERT|TLS_SHA256)=' /root/rqlite-credentials.txtRQLITE_USER=admin RQLITE_URL=https://127.0.0.1:4001 RQLITE_CONSOLE_URL=https://127.0.0.1:4001/console/ RQLITE_TLS_CERT=/etc/rqlite/tls/server.crt RQLITE_TLS_SHA256=CC:87:14:E0:A0:DE:93:27:BC:91:DD:11:19:B0:2D:8B:34:90:FA:4F:96:81:AE:01:D4:CC:3D:70:A0:02:B4:11Show the password itself when you need it (for the web console or a client on your own computer):
sudo grep '^RQLITE_PASSWORD=' /root/rqlite-credentials.txtRQLITE_TLS_SHA256 is the SHA-256 fingerprint of this VM's TLS certificate (RQLITE_TLS_CERT). Compare it with what your browser or client shows the first time it connects. It is different on every VM.
Run SQL
rqlite-query sends SQL to rqlite over HTTPS as admin and trusts this VM's certificate. It hands the password from the credentials file to curl in a configuration file rather than on a command line. Each argument is one statement, and --tx runs them in one transaction.
Create a table and add some rows:
sudo rqlite-query --tx "CREATE TABLE orders (id INTEGER PRIMARY KEY, customer TEXT NOT NULL, region TEXT NOT NULL, cents INTEGER NOT NULL)" "INSERT INTO orders (customer, region, cents) VALUES ('Ada', 'EMEA', 25025), ('Grace', 'AMER', 99999), ('Alan', 'EMEA', 44780), ('Hedy', 'APAC', 12000), ('Linus', 'APAC', 8050)"{ "results": [ { "rows": null }, { "last_insert_id": 5, "rows_affected": 5, "rows": null } ] }Query them:
sudo rqlite-query "SELECT region, COUNT(*) AS orders, SUM(cents) / 100.0 AS total FROM orders GROUP BY region ORDER BY total DESC"{ "results": [ { "types": { "orders": "integer", "region": "text", "total": "real" }, "rows": [ { "orders": 1, "region": "AMER", "total": 999.99 }, { "orders": 2, "region": "EMEA", "total": 698.05 }, { "orders": 2, "region": "APAC", "total": 200.5 } ] } ] }Every write is committed through Raft before rqlite answers. For applications, prefer parameterized statements, which the HTTP API supports directly. For example, post [["INSERT INTO orders (customer, region, cents) VALUES (?, ?, ?)", "Ada", "EMEA", 25025]] to /db/execute. See the rqlite API documentation for details.
Check the node
rqlite-query --get calls any API path. The /readyz path reports whether the node, the Raft leader and the store are ready:
sudo rqlite-query --get /readyz[+]node ok [+]leader ok [+]store ok [+]db okThe /status path returns everything about the node as JSON. This example prints the version, the Raft state and the API's security settings:
sudo rqlite-query --get /status | python3 -c 'import json, sys; d = json.load(sys.stdin); print(d["build"]["version"], d["store"]["raft"]["state"], "auth", d["http"]["auth"], "tls", d["http"]["tls"]["enabled"], d["http"]["bind_addr"])'v10.6.4 Leader auth enabled tls true 127.0.0.1:4001Open the web console through an SSH tunnel
rqlite's built-in web console shows the cluster and your schema, runs queries and takes backups. rqlite serves it at /console/ on the same HTTPS port, so you reach it through an SSH tunnel from your computer. Open the tunnel:
ssh -N -L 4001:127.0.0.1:4001 azureuser@<vm-public-ip>Then open https://127.0.0.1:4001/console/ in your browser. You can also use https://localhost:4001/console/, because the certificate names both.
The certificate is self-signed and unique to your VM, so the browser warns about it the first time. Check that the SHA-256 fingerprint it shows matches RQLITE_TLS_SHA256 from Step 5, then continue. To avoid the warning, copy /etc/rqlite/tls/server.crt to your computer and trust it for localhost.
The browser asks for a user name and password. Sign in as admin with the password from Step 5.
The Home page shows the cluster. This node is the leader of a one-node cluster, its API address is HTTPS and Raft is on the loopback address. The HTTP and Raft panels on Home show how the API is exposed: bound to 127.0.0.1:4001, with authentication enabled and TLS on.
Query runs SQL, for example the query from Step 6. Schema shows every table and its columns.
Backup downloads a copy of the database as a SQLite file, and Restore loads one. Both act on the whole database, so use them with care.
Connect applications from your computer
With the tunnel from Step 8 open, applications on your computer connect to https://localhost:4001 with HTTP basic authentication as admin. First copy the VM's certificate so that your client can verify it:
ssh azureuser@<vm-public-ip> cat /etc/rqlite/tls/server.crt > rqlite-vm.crt curl --cacert rqlite-vm.crt -u admin -G https://localhost:4001/db/query --data-urlencode 'q=SELECT COUNT(*) FROM orders'curl -u admin asks for the password, so it does not end up on a command line or in your shell history. rqlite's client libraries take an HTTPS URL, a user and a password the same way. Examples are pyrqlite for Python, gorqlite for Go and rqlite-js for JavaScript. Give them the password from a file or a secret store, not as a command-line argument.
The rqlite shell is also on the VM and on rqlite's release downloads. It accepts credentials only as -u admin:<password> on its command line, where other local users can see them while it runs and your shell may record them. Prefer rqlite-query, the console or a client library. If you use the shell, connect like this: rqlite -s https -H localhost -p 4001 -c rqlite-vm.crt -u admin:<password>.
Open rqlite to your virtual network (optional)
For applications inside your Azure virtual network, the SSH tunnel (or a bastion host) keeps the database ports closed. If you decide to expose the API to your network instead, do the following:
- Listen on the VM's private address as well. Override the
ExecStartline ofrqlited.service(sudo systemctl edit --full rqlited), for example with-http-addr 0.0.0.0:4001 -http-adv-addr <vm-private-ip>:4001in place of-http-addr 127.0.0.1:4001. Keep-auth,-http-certand-http-key. The certificate already names the VM's private address. Then runsudo systemctl restart rqlited. - Allow TCP 4001 in the NSG from one CIDR only (your application subnet), never from
*orInternet:
az network nsg rule create --resource-group <your-rg> --nsg-name <your-nsg> \
--name allow-rqlite-api-from-app-subnet --priority 310 --direction Inbound --access Allow \
--protocol Tcp --source-address-prefixes <your-cidr> --destination-port-ranges 4001Leave Raft (4002) on the loopback address unless you build a cluster (Step 13).
Security checks
rqlite and its first boot service cannot reach the Azure Instance Metadata Service or the WireServer. Check the blocked addresses:
systemctl show -p IPAddressDeny --value rqlited.service | tr ' ' '\n' | sort168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128Run the image's egress probe inside the control group of rqlited.service as the rqlite user. It shows both endpoints blocked while loopback still works:
sudo bash -c 'echo $$ > /sys/fs/cgroup$(systemctl show -p ControlGroup --value rqlited.service)/cgroup.procs; exec setpriv --reuid=rqlite --regid=rqlite --init-groups /usr/local/sbin/sac-egress-probe rqlited.service'EGRESS-PROBE rqlited.service loopback=REACHABLE imds4=TimeoutError imds4mapped=TimeoutError imds6=ENETUNREACH wireserver=TimeoutError wireservermapped=TimeoutError verdict=BLOCKEDA request without credentials is refused, plaintext HTTP is refused, and the self-signed certificate is not trusted unless you name it. Curl exit code 60 is a certificate error:
curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/rqlite/tls/server.crt https://localhost:4001/status
curl -s http://127.0.0.1:4001/status; echo
curl -s -o /dev/null https://localhost:4001/status; echo "curl exit code $?"401 Client sent an HTTP request to an HTTPS server. curl exit code 60Only root can read the password. Only root and the rqlite service group can read the authentication file and the TLS key. rqlite itself runs unprivileged:
sudo stat -c '%a %U:%G %n' /etc/rqlite/auth.json /etc/rqlite/tls/server.key /root/rqlite-credentials.txt ps -o user:10=,comm= -u rqlite
sudo grep -E '^(CapEff|NoNewPrivs)' /proc/$(systemctl show -p MainPID --value rqlited.service)/status640 root:rqlite /etc/rqlite/auth.json 640 root:rqlite /etc/rqlite/tls/server.key 600 root:root /root/rqlite-credentials.txt rqlite rqlited CapEff: 0000000000000000 NoNewPrivs: 1Check how this rqlite was built, and read the build-time vulnerability scan:
rqlited -version sed -n '1,4p' /usr/share/doc/sac/rqlite-BUILD-PROVENANCE.txt grep -c 'No vulnerabilities found' /usr/share/doc/sac/rqlite-govulncheck.txtrqlited v10.6.4 linux amd64 go1.27.3 sqlite3.53.4 (commit 64145717224d620809174cb2235ac86ec604aac5, compiler gc) rqlite v10.6.4 on this image was compiled by sac from the unmodified upstream source: repository https://github.com/rqlite/rqlite (MIT) tag v10.6.4 (commit 64145717224d620809174cb2235ac86ec604aac5; go.mod sha256 d4187d82ca17de2cce2c2bdf64cf70211617dcfdecfe7279f5248337cc9c640a; go.sum sha256 b200367b9ee837f5e05a0b2fcc69a64b2fb89f9e5ab44c5689b43bf518477788) toolchain go1.27.3 linux/amd64, CGO_ENABLED=1 (SQLite 3.53.4, vendored by github.com/rqlite/go-sqlite3) 2govulncheck checks the code each binary actually links. For the rqlite shell, it notes one advisory in a module the build requires, GO-2026-5932 (golang.org/x/crypto/openpgp, unmaintained, no fix). It reports that the code does not call it, because the shell links only x/crypto/ssh/terminal from that module. rqlited, the server, has no finding at all.
The image also ships its own round-trip check. The per-VM password reads and writes through Raft, using a transactional, parameterized write and an aggregate read on a scratch table that the check drops again. Anonymous, wrong, blank, common and unknown-user credentials are refused with 401. The check also confirms that plaintext is refused, that the certificate is needed and that both ports are loopback only:
sudo /usr/local/sbin/rqlite-roundtrip.sh | tail -1OK-rqlite-roundtripChange the password
rqlite-rotate-password gives admin a new password. It writes the password to /etc/rqlite/auth.json and the credentials file, restarts rqlite, and checks that the new password works and the old one is refused. The password never appears on a command line.
To set a new random 32-character password, run:
sudo rqlite-rotate-passwordrqlite-rotate-password: the password of 'admin' was changed; the new one reads (200) and the old one is refused (401). It is in /root/rqlite-credentials.txt.To choose the password yourself, type it at a prompt that does not echo and pass it to the helper on standard input. Use 16 to 128 printable characters, with no space, double quote or backslash:
read -rs -p 'New rqlite password: ' NEWPW; echo printf '%s\n' "$NEWPW" | sudo rqlite-rotate-password --stdin; unset NEWPWAvoid passing a password to sudo on a command line, because sudo records its command line in /var/log/auth.log. rqlite 10.6.4 stores passwords in auth.json in plain text, which is why only root and the rqlite group can read the file.
You can add further users with narrower permissions by editing auth.json. Use ["query", "execute"] for an application, or ["query"] for read-only reporting. See rqlite's security documentation for the permission names.
Never add a user named *. rqlite grants that user's permissions to anonymous requests, and the image's start-up check refuses to start rqlite with one. The check also expects exactly one user, so if you add users, remove the check from rqlited.service with sudo systemctl edit rqlited (an empty ExecStartPre= line) and keep the file's permissions.
Growing into a cluster
A fault-tolerant rqlite cluster has three or more nodes, and a majority must be up to accept writes. The image is configured as a single node. To build a cluster from several VMs of this image, follow this outline:
- Put the nodes in one subnet and allow TCP 4001 and 4002 in the NSG from that subnet only, never from
*orInternet. - Create your own certificate authority and a certificate for each node that names its private IP address. This encrypts and mutually authenticates Raft traffic between nodes. Give each node the CA so that the HTTPS API can verify its peers.
- On each node, change the
ExecStartline ofrqlited.service(sudo systemctl edit --full rqlited) to listen on the private address and join the first node, for example:
/usr/local/bin/rqlited -http-addr 0.0.0.0:4001 -http-adv-addr <vm-private-ip>:4001 \
-raft-addr 0.0.0.0:4002 -raft-adv-addr <vm-private-ip>:4002 \
-auth /etc/rqlite/auth.json -join-as admin -join <first-node-private-ip>:4002 \
-http-cert /etc/rqlite/tls/server.crt -http-key /etc/rqlite/tls/server.key -http-ca-cert /etc/rqlite/tls/ca.crt \
-node-cert /etc/rqlite/tls/node.crt -node-key /etc/rqlite/tls/node.key -node-ca-cert /etc/rqlite/tls/ca.crt -node-verify-client \
/var/lib/rqlite/data- Use the same
auth.jsonon every node, and join nodes that start with an empty data directory. On a fresh VM, stop rqlite and empty/var/lib/rqlite/databefore its first join.
rqlite's clustering and security documentation describes each flag, automatic bootstrapping (-bootstrap-expect) and read-only nodes.
Backup, updates and maintenance
Back up by snapshotting the data disk. Stop rqlite first for a fully consistent copy with sudo systemctl stop rqlited. For example, run az snapshot create --resource-group <your-rg> --name rqlite-data-snap --source <data-disk-name>.
Alternatively, download a consistent SQLite copy of the database while rqlite runs, from the console's Backup page or the API. In this example, the password goes to curl on standard input, not on its command line:
sudo sh -c 'printf "user = \"admin:%s\"\n" "$(sed -n "s/^RQLITE_PASSWORD=//p" /root/rqlite-credentials.txt)" | curl -sS -K - --cacert /etc/rqlite/tls/server.crt -o /root/rqlite-backup.sqlite https://localhost:4001/db/backup'
sudo head -c 15 /root/rqlite-backup.sqlite; echoSQLite format 3rqlite can also back up automatically to S3-compatible object storage (-auto-backup, see rqlite's backup documentation). This image blocks rqlite from the Azure metadata service, so that feature needs an access key in its configuration file rather than a managed identity. Disk snapshots need no credentials on the VM.
- Logs: run sudo journalctl -u rqlited.service -n 50.
- Updates: Ubuntu security updates install automatically. rqlite is not an apt package. To move to a newer rqlite, snapshot the data disk. Then replace /usr/local/bin/rqlited and /usr/local/bin/rqlite with the binaries from the new release on rqlite's GitHub releases page, after you check the release's SHA-256 digest. Restart by running sudo systemctl stop rqlited, installing the two files with mode 0755, and running sudo systemctl start rqlited.
- Read the release notes first. SecureAnyCloud also publishes new images as rqlite releases.
- The build provenance of the shipped binaries is in /usr/share/doc/sac/rqlite-BUILD-PROVENANCE.txt.
Support and licensing
rqlite is open source software under the MIT license, and SQLite is in the public domain. The license of rqlite and of every Go module linked into it (MIT, Apache-2.0, BSD and MPL-2.0 licenses) is in /usr/share/doc/sac/rqlite-third-party-licences/, with a notice saying where to obtain the source of each module. The rqlite on this image is built from the unmodified upstream source.
SecureAnyCloud provides 24/7 technical support for this image by email and live chat: sales@secureanycloud.com.
rqlite and SQLite are trademarks of their respective owners. SecureAnyCloud is not affiliated with or endorsed by the rqlite project or the SQLite developers.
Deploy on Azure
Launch rqlite 10.6.4 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
This SecureAnyCloud image ships rqlite 10.2.7, configured and ready to answer queries from the first boot of every instance you launch.
rqlite is a lightweight, distributed relational database that uses SQLite as its storage engine and the Raft consensus protocol to replicate writes across nodes. You get real relational SQL with transactions and the full SQLite type system, reached over a simple HTTP API that returns JSON, plus an interactive command-line client. It runs well as a single node, which is how this image ships, and scales out to a fault-tolerant cluster when you need one.
What makes this image different
The image contains no database at all. Most database images ship a database that was created on the build machine. Build-time rows, a schema and, worse, a build-time credential then ride into every customer instance. This image does not. The entire data directory, holding the SQLite database, the Raft log, the Raft snapshots and the node identity, is destroyed before capture.
A first boot service creates a pristine data directory on your instance and generates a credential unique to it. There is no default login anywhere in the image and nothing to inherit.
Authentication is on, from the first packet. Out of the box, rqlite ships with no authentication at all. If no authentication file is configured, every request is permitted. This image never runs that way. First boot generates a random 32-character credential unique to your instance, and rqlite is started with authentication always enabled. An unauthenticated request is refused with 401.
rqlite refuses to start until that has happened. Two independent mechanisms enforce it, so a failure to generate a credential can never leave a database running without one.
Loopback only by default. The HTTP API and the Raft port are bound to 127.0.0.1, and the instance security group opens port 22 only. A database reachable from the network the moment it boots is a materially larger attack surface than a documented opt-in. Section 8 shows how to open it to your own VPC when you need to.
1. Launch the instance
Subscribe to the listing in AWS Marketplace and launch it, or launch the AMI directly with the AWS CLI. The recommended instance type is m5.large or larger. For example:
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type m5.large \
--key-name my-key \
--security-group-ids <sg-id> \
--metadata-options 'HttpTokens=required'The image declares a dedicated 32 GiB EBS data volume. AWS provisions it automatically and mounts it at /var/lib/rqlite. Your database, Raft log and snapshots therefore live on their own volume rather than on the operating system disk, and you can snapshot or resize that volume independently.
2. Connect to your instance
Connect over SSH as the default login user for your operating system variant:
OS variant SSH login user Connect
Ubuntu 24.04 LTS ubuntu ssh ubuntu@<instance-ip>
ssh ubuntu@<instance-ip>3. Confirm rqlite is running
systemctl status rqlited.service --no-pagerThe service is a native systemd unit and starts automatically on boot. The drop-in shown in the output enforces the first boot credential generation described above.
Confirm the version, the storage engine and the exact upstream commit that this binary was built from:
rqlited -versionrqlited v10.2.7 linux amd64 go1.26.5 sqlite3.53.2 (commit a66a6f36d3b5a7f0da0bad3768014052a74ce4bc, compiler gc)4. Retrieve your credential
First boot generates a random credential and writes it to a file that only root can read.
sudo cat /root/rqlite-credentials.txtExpected output, with a password value that is unique to your instance:
# rqlite per-VM credential -- generated on first boot, unique to this instance.
# The HTTP API is bound to loopback (127.0.0.1:4001) and requires this credential. RQLITE_USERNAME=sac RQLITE_PASSWORD=<RQLITE_PASSWORD>The same credential is held in /etc/rqlite/auth.json, which only root and the rqlite service account can read.
5. Run your first query
rqlite exposes an HTTP API that takes SQL and returns JSON. Every request must authenticate:
curl -sS -u 'sac:<RQLITE_PASSWORD>' -G 'http://localhost:4001/db/query' \
--data-urlencode 'q=SELECT sqlite_version()' | jq .Confirm that an unauthenticated request is genuinely refused:
curl -sS -o /dev/null -w 'anonymous request returns HTTP %{http_code}\n' \
-G 'http://localhost:4001/db/query' --data-urlencode 'q=SELECT 1'anonymous request returns HTTP 401The interactive client is also installed. It reads from standard input, so when you use it inside a script, redirect from /dev/null or pass -c:
rqlite -u 'sac:<RQLITE_PASSWORD>' -H localhost -p 4001 </dev/null <<'SQL' .tables SQL6. Create a schema and write real data
Writes go to /db/execute as a JSON array of statements. Parameterized statements are supported, and they are the right way to pass values.
curl -sS -u 'sac:<RQLITE_PASSWORD>' -H 'Content-Type: application/json' \
-d '[ ["CREATE TABLE IF NOT EXISTS station (id INTEGER PRIMARY KEY, name TEXT NOT NULL, region TEXT NOT NULL, opened INTEGER)"], ["CREATE INDEX IF NOT EXISTS idx_station_region ON station(region)"], ["INSERT INTO station(name, region, opened) VALUES(?, ?, ?)", "Kings Cross", "London", 1852], ["INSERT INTO station(name, region, opened) VALUES(?, ?, ?)", "Waverley", "Edinburgh", 1846], ["INSERT INTO station(name, region, opened) VALUES(?, ?, ?)", "Piccadilly", "Manchester", 1842], ["INSERT INTO station(name, region, opened) VALUES(?, ?, ?)", "Temple Meads", "Bristol", 1840] ]' \
'http://localhost:4001/db/execute?pretty' | jq '.results[] | {rows_affected: .rows_affected, last_insert_id: .last_insert_id}'Read the rows back with a query:
curl -sS -u 'sac:<RQLITE_PASSWORD>' -G 'http://localhost:4001/db/query?pretty' \
--data-urlencode 'q=SELECT region, name, opened FROM station ORDER BY opened' | jq .An aggregate over the same table:
curl -sS -u 'sac:<RQLITE_PASSWORD>' -G 'http://localhost:4001/db/query?pretty' \
--data-urlencode 'q=SELECT COUNT(*) AS stations, MIN(opened) AS earliest, MAX(opened) AS latest FROM station' | jq .rqlite supports real transactions. Pass transaction, and the whole batch either commits or rolls back together:
curl -sS -u 'sac:<RQLITE_PASSWORD>' -H 'Content-Type: application/json' \
-d '[ ["UPDATE station SET region = ? WHERE name = ?", "Greater London", "Kings Cross"], ["INSERT INTO station(name, region, opened) VALUES(?, ?, ?)", "Haymarket", "Edinburgh", 1842] ]' \
'http://localhost:4001/db/execute?transaction&pretty' | jq '.results[] | {rows_affected: .rows_affected}'7. Inspect Raft consensus and node status
Even as a single node, rqlite runs a real Raft state machine. The node elects itself leader, and every write is committed through the Raft log before it is applied to SQLite. A node that never achieves leadership can accept connections but cannot serve a write, so this is the check that matters.
curl -sS -u 'sac:<RQLITE_PASSWORD>' 'http://localhost:4001/status' \
| jq '{raft_state: .store.raft.state, raft_term: .store.raft.term, leader: .store.leader.addr, http_bind: .http.bind_addr, auth: .http.auth}'{ "raft_state": "Leader", "raft_term": 4, "leader": "localhost:4002", "http_bind": "127.0.0.1:4001", "auth": "enabled" }Note "auth": "enabled". This is rqlite itself confirming that authentication is active.
List the nodes in the cluster and their reachability:
curl -sS -u 'sac:<RQLITE_PASSWORD>' 'http://localhost:4001/nodes?pretty' | jq .Use a readiness probe for a load balancer or a health check:
curl -sS -o /dev/null -w 'readyz returns HTTP %{http_code}\n' \
-u 'sac:<RQLITE_PASSWORD>' 'http://localhost:4001/readyz'8. Network posture, and opening rqlite to your VPC
By default both ports are bound to loopback only:
ss -tln | grep -E '400[12]'LISTEN 0 4096 127.0.0.1:4001 0.0.0.0:* LISTEN 0 4096 127.0.0.1:4002 0.0.0.0:*Port 4001 is the HTTP API, and port 4002 is Raft. The instance security group opens port 22 only. As shipped, neither database port is reachable from outside the instance.
Reaching rqlite from your workstation without opening anything
The simplest safe path is an SSH tunnel, which exposes nothing to the network:
ssh -L 4001:localhost:4001 ubuntu@<instance-ip>Then point any client at http://localhost:4001 on your own machine.
Opening the API to your own VPC
When an application elsewhere in your VPC needs to reach rqlite, change the bind address and open the port to that network only. Never open a database port to 0.0.0.0/0.
First edit the service unit:
sudo systemctl edit rqlited.serviceAdd an override that binds the API to all interfaces and advertises the address that other hosts should use. Substitute your instance's private address:
[Service] ExecStart= ExecStart=/usr/local/bin/rqlited \
-http-addr 0.0.0.0:4001 \
-http-adv-addr <private-ip>:4001 \
-raft-addr localhost:4002 \
-auth /etc/rqlite/auth.json \
/var/lib/rqlite/dataThen reload and restart. Add an inbound rule to the instance security group that allows port 4001 from your VPC CIDR only:
sudo systemctl daemon-reload && sudo systemctl restart rqlited aws ec2 authorize-security-group-ingress --group-id <sg-id> \
--protocol tcp --port 4001 --cidr 10.0.0.0/16Because authentication is already enabled, the API is authenticated from the moment you expose it. Also consider enabling HTTPS with -http-cert and -http-key before you carry traffic across a network.
Forming a real Raft cluster
This image ships a single node, which is a complete working database. Raft replication needs at least three nodes to tolerate the loss of one.
To grow a cluster, launch further instances from this same image. On each additional node, set a unique node identity, advertise a routable Raft address and join the first node:
-node-id <unique-node-id> \
-http-addr 0.0.0.0:4001 -http-adv-addr <this-node-private-ip>:4001 \
-raft-addr 0.0.0.0:4002 -raft-adv-addr <this-node-private-ip>:4002 \
-join http://<first-node-private-ip>:4001Every node must carry the same auth.json, and the joining node needs a credential with the join permission. Open ports 4001 and 4002 between the cluster members only, inside your VPC, and never to the internet.
9. Back up your database
rqlite serves a consistent backup of the underlying SQLite database over the API. Download it:
sudo curl -sS -u 'sac:<RQLITE_PASSWORD>' 'http://localhost:4001/db/backup' -o /var/lib/rqlite/backup.sqlite ls -lh /var/lib/rqlite/backup.sqliteThe result is an ordinary SQLite file. Any SQLite tool can read it, and you can restore it into a fresh node with the /db/load endpoint. Copy it to Amazon S3 or another durable location on a schedule that suits you, or take an EBS snapshot of the data volume.
10. Keeping rqlite patched
The operating system and every apt-managed component on this image patch normally with apt-get update && apt-get upgrade. Unattended upgrades run exactly as they do on stock Ubuntu.
rqlite itself is not an apt package. The project does not publish an apt repository, so apt-get upgrade does not move it. The image documents the update path on the machine itself:
cat /usr/share/doc/sac/RQLITE-UPDATE-PATH.txtIn short, you have three supported routes:
- Drop in the upstream binary. rqlite publishes a tarball for every release. Verify its checksum, then install
rqlitedandrqliteover/usr/local/binand restart the service. Do not install the upstream.debover this image, because it would place a secondrqlitedat/usr/binalong with a conflicting systemd unit. - Take the republished SecureAnyCloud image, which is rebuilt from source on a freshly patched base.
- Rebuild from source. rqlite is MIT licensed, so you have the right to do this, and the document above carries the exact commands.
The binary on this image was compiled from the upstream MIT source at commit a66a6f36d3b5a7f0da0bad3768014052a74ce4bc with Go 1.26.5. That toolchain carries fixes that the upstream prebuilt 10.2.7 binary predates.
11. Licenses and source availability
rqlite is MIT licensed. Its storage engine, SQLite, is in the public domain. Several components linked into rqlited, including the HashiCorp Raft implementation, are licensed under the Mozilla Public License 2.0.
The image carries the full license text of every component linked into the binary. A notice names each component, its version and where to obtain its source. Read the notice:
cat /usr/share/doc/sac/rqlite-third-party-licences/NOTICE.txtUnder section 3.2 of the Mozilla Public License 2.0, you may obtain the source of any MPL 2.0 component from its module path through any Go module proxy, or by contacting sales@secureanycloud.com.
Support
SecureAnyCloud supports this image. If you hit a problem with the image, contact sales@secureanycloud.com. For questions about rqlite itself, see the upstream project documentation at rqlite.io.
Deploy on AWS
Launch rqlite Distributed SQLite Database | Support by SecureAnyCloud with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com