Databases AWS

rqlite 10.6.4 on AWS User Guide

| Product: rqlite 10.6.4 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using rqlite 10.6.4 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of cloudimg's distributed and embedded SQL databases listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other databases in the listing are described on the listing's guide hub.

rqlite is a lightweight, open source distributed relational database that uses SQLite as its storage engine and the Raft consensus protocol to replicate it. You talk to it 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 of several nodes when you need one.

What the cloudimg image adds:

  • 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 instance, and nothing is granted to anonymous requests. Every API path and the web console answer an unauthenticated or wrong-password request with 401. 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 instance 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 is reachable; you reach rqlite through an SSH tunnel (Step 7).
  • Built from source, scanned. rqlite 10.6.4 (a security release) is compiled on the image from the unmodified upstream source at the v10.6.4 tag with Go 1.27.3, every dependency exactly as upstream pins it, and checked with govulncheck at build time (no known vulnerabilities in the code it links). The build provenance, the scan report and the licence of every linked component ship on the image.
  • Hardened service. rqlite runs as the unprivileged rqlite user with no capabilities, a read-only view of the system and no access to /root, and it cannot reach the instance metadata service; the AMI requires IMDSv2. A start-up check refuses to start rqlite before first boot, without its data volume or TLS, or with an authentication file that would allow anonymous access. SSH accepts keys only, and fwupd's automatic firmware-metadata refresh is switched off (EC2 has no firmware it can update).
  • Your data on its own disk. The data directory (/var/lib/rqlite: the SQLite database, the Raft log and snapshots) is on a separate 20 GiB EBS volume you can grow or snapshot on its own. The image ships it empty: each instance 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), a helper rqlite-query that runs SQL and API calls with your credentials, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. The database is empty, ready for your tables.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

What this is not

This is a single node rqlite. A single node is a complete database (reads and writes go through Raft and SQLite as usual) but it is not fault tolerant; Step 11 outlines how 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 AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group 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.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick rqlite 10.6.4 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (or larger), your key pair and a security group as above. The AMI brings two volumes: the root volume and a 20 GiB data volume (/dev/sdf) for /var/lib/rqlite. Make the data volume as large as your data needs at launch.

With the AWS CLI (replace the values in angle brackets; this example grows the data volume to 50 GiB):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --block-device-mappings 'DeviceName=/dev/sdf,Ebs={VolumeSize=50,VolumeType=gp3}' \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=rqlite}]'

If you make the data volume larger at launch, grow the file system once after first boot with sudo resize2fs "$(findmnt -no SOURCE /var/lib/rqlite)". First boot takes a few seconds once the instance is running.

Step 2: Connect to your instance

Version SSH user
rqlite 10.6.4 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

The first boot service and rqlite should both be active:

systemctl is-active rqlite-firstboot.service rqlited.service
active
active

rqlite listens on the loopback address only; from the network, only SSH is reachable:

sudo ss -Htln
LISTEN 0      4096         0.0.0.0:22   0.0.0.0:*
LISTEN 0      4096      127.0.0.54:53   0.0.0.0:*
LISTEN 0      4096       127.0.0.1:4002 0.0.0.0:*
LISTEN 0      4096       127.0.0.1:4001 0.0.0.0:*
LISTEN 0      4096   127.0.0.53%lo:53   0.0.0.0:*
LISTEN 0      4096            [::]:22      [::]:*

4001 is the HTTPS API and web console, 4002 is Raft; the port 53 lines are Ubuntu's local DNS resolver, also loopback only.

Step 4: Get your password

Your connection details are in /root/rqlite-credentials.txt:

sudo grep -E '^RQLITE_(USER|URL|CONSOLE_URL|TLS_CERT|TLS_SHA256)=' /root/rqlite-credentials.txt
RQLITE_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=72:49:94:70:1B:21:81:69:E3:77:8C:20:9D:2F:22:ED:0E:6E:E2:DF:49:AE:B8:7E:88:47:0F:4D:1F:F6:78:34

Show 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.txt

RQLITE_TLS_SHA256 is the SHA-256 fingerprint of this instance's TLS certificate (RQLITE_TLS_CERT): compare it with what your browser or client shows the first time it connects.

Step 5: Run SQL

rqlite-query sends SQL to rqlite over HTTPS as admin, trusting this instance's certificate, with the password from the credentials file handed to curl in a configuration file rather than on a command line. Each argument is one statement; --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 parameterised statements, which the HTTP API supports directly (for example [["INSERT INTO orders (customer, region, cents) VALUES (?, ?, ?)", "Ada", "EMEA", 25025]] posted to /db/execute); see the rqlite API documentation.

Step 6: Check the node

rqlite-query --get calls any API path. /readyz reports that the node, the Raft leader and the store are ready:

sudo rqlite-query --get /readyz
[+]node ok
[+]leader ok
[+]store ok
[+]db ok

/status returns everything about the node as JSON; for example, 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:4001

Step 7: Open the web console through an SSH tunnel

rqlite's built-in web console shows the cluster, your schema, runs queries and takes backups. It is served by rqlite itself at /console/ on the same HTTPS port, so you reach it through an SSH tunnel from your computer:

ssh -i <your-key.pem> -N -L 4001:127.0.0.1:4001 ubuntu@<public-ip>

Then open https://127.0.0.1:4001/console/ (or https://localhost:4001/console/; the certificate names both) in your browser. The certificate is self-signed and unique to your instance, so the browser warns about it the first time: check that the SHA-256 fingerprint it shows matches RQLITE_TLS_SHA256 from Step 4, 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 4.

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 rqlite web console Home page: a one-node cluster, 1/1 reachable, this node 127.0.0.1:4002 is the leader, API address https://127.0.0.1:4001, version v10.6.4, ready, with the topology view showing the single leader node

Query runs SQL; here the query from Step 5:

The rqlite web console Query page with SELECT region, COUNT(*) AS orders, SUM(cents) / 100.0 AS total FROM orders GROUP BY region ORDER BY total DESC run, showing AMER 1 999.99, EMEA 2 698.05 and APAC 2 200.5

Schema shows every table and its columns:

The rqlite web console Schema page showing the table orders with its four columns: id INTEGER primary key, customer TEXT not null, region TEXT not null and cents INTEGER not null

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.

Step 8: Connect applications from your computer

With the tunnel from Step 7 open, applications on your computer connect to https://localhost:4001 with HTTP basic authentication as admin. Copy the instance's certificate first so your client can verify it:

ssh -i <your-key.pem> ubuntu@<public-ip> cat /etc/rqlite/tls/server.crt > rqlite-instance.crt
curl --cacert rqlite-instance.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 in your shell history. rqlite's client libraries (for example pyrqlite for Python, gorqlite for Go and rqlite-js for JavaScript) take an HTTPS URL, a user and a password the same way.

The rqlite shell is also on the instance 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; on a shared machine prefer rqlite-query, the console or a client library. If you use it, connect like this: rqlite -s https -H localhost -p 4001 -c rqlite-instance.crt -u admin:<password>.

For applications inside your VPC, the same tunnel (or an SSH-based bastion) keeps the database ports closed to the network. If you decide to expose the API to your VPC instead, change the listen address as shown in Step 11 and allow TCP 4001 in the security group from your application subnets only, never 0.0.0.0/0.

Step 9: Security checks

rqlite and its first boot service cannot reach the instance metadata service:

systemctl show -p IPAddressDeny rqlited.service rqlite-firstboot.service
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

A 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 60

The password, the authentication file and the TLS key are readable by root (and, for the last two, the rqlite service group) only, and rqlite 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)/status
640 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: 1

How this rqlite was built, and the build-time vulnerability scan:

rqlited -version
sed -n '1,4p' /usr/share/doc/cloudimg/rqlite-BUILD-PROVENANCE.txt
grep -c 'No vulnerabilities found' /usr/share/doc/cloudimg/rqlite-govulncheck.txt
rqlited 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 cloudimg 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)
2

The image's own round trip: the per-instance password reads and writes through Raft (a transactional, parameterised write and an aggregate read on a scratch table it drops again), while anonymous, wrong, blank, common and unknown-user credentials are refused with 401; it also checks that plaintext is refused, that the certificate is needed and that both ports are loopback only:

sudo /usr/local/sbin/rqlite-roundtrip.sh | tail -1
OK-rqlite-roundtrip

Step 10: Change the password

rqlite reads users and passwords from /etc/rqlite/auth.json (rqlite 10.6.4 stores them in plain text, which is why the file is readable by root and the rqlite group only). Edit the password there and in the credentials file with an editor, then restart rqlite:

sudo nano /etc/rqlite/auth.json          # change the "password" value
sudo nano /root/rqlite-credentials.txt   # change RQLITE_PASSWORD to the same value
sudo systemctl restart rqlited

Use a long random password (for example from openssl rand -base64 32), and avoid passing it to sudo on a command line, which would record it in /var/log/auth.log. You can add further users with narrower permissions (for example ["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 its 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 that check from rqlited.service with sudo systemctl edit rqlited (an empty ExecStartPre= line) and keep the file's permissions.

Step 11: Growing into a cluster

A fault-tolerant rqlite cluster has three or more nodes (a majority must be up to accept writes). The image is configured as a single node; to build a cluster from several instances of it, in outline:

  • Put the nodes in one security group that allows TCP 4001 and 4002 from itself only (the group as the source), never from 0.0.0.0/0.
  • Create your own certificate authority and a certificate for each node naming its private IP address, so that Raft traffic between nodes is encrypted and mutually authenticated, and give each node the CA so the HTTPS API can verify its peers.
  • On each node, change the ExecStart line of rqlited.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 <private-ip>:4001 \
  -raft-addr 0.0.0.0:4002 -raft-adv-addr <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.json on every node, and join nodes that start with an empty data directory (stop rqlite and empty /var/lib/rqlite/data on a fresh instance before its first join).

rqlite's clustering and security documentation describes each flag, automatic bootstrapping (-bootstrap-expect) and read-only nodes.

Step 12: Backup, updates and maintenance

  • Back up by snapshotting the data volume (stop rqlite first for a fully consistent copy: sudo systemctl stop rqlited), or download a consistent SQLite copy of the database while it runs, from the console's Backup page or the API:
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'

rqlite can also back up automatically to Amazon S3 (-auto-backup); this image blocks rqlite from the instance metadata service, so that feature needs an access key in its configuration file rather than the instance role. EBS snapshots of the data volume need no credentials on the instance. * Logs: 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, take a snapshot of the data volume, then replace /usr/local/bin/rqlited and /usr/local/bin/rqlite with the binaries from the new release on rqlite's GitHub releases page (check the release's SHA-256 digest first) and restart: sudo systemctl stop rqlited, install the two files with mode 0755, sudo systemctl start rqlited. Read the release notes first; cloudimg also publishes new images as rqlite releases. The build provenance of the shipped binaries is in /usr/share/doc/cloudimg/rqlite-BUILD-PROVENANCE.txt.

Support and licensing

rqlite is open source software under the MIT licence; SQLite is in the public domain. The licence of rqlite and of every Go module linked into it (MIT, Apache-2.0, BSD and MPL-2.0 licences) is in /usr/share/doc/cloudimg/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. cloudimg provides 24/7 technical support for this image by email and live chat: support@cloudimg.co.uk. rqlite and SQLite are trademarks of their respective owners; cloudimg is not affiliated with or endorsed by the rqlite project or the SQLite developers.