XTDB 2.1.0
Overview
Newer version available. XTDB 2.2.0 replaces this version in the listing. For new launches choose XTDB 2.2.0 on Ubuntu 24.04 and follow the XTDB 2.2.0 guide. This page stays available for instances already running XTDB 2.1.0.
XTDB is an immutable, bitemporal SQL database. Every row keeps two timelines: valid time (when a fact is true in the real world) and system time (when the database recorded it). Updates and deletes never overwrite history. You can ask what a row said on any past date (FOR VALID_TIME AS OF ...), see every version of a row (FOR ALL VALID_TIME) or audit exactly what the database knew at any moment (FOR SYSTEM_TIME AS OF ...), all without trigger tables or soft-delete columns. XTDB 2 speaks the PostgreSQL wire protocol, so psql, JDBC and the usual PostgreSQL drivers connect to it unchanged.
This SecureAnyCloud image runs XTDB 2.1.0 on Ubuntu 24.04 on Amazon EC2. It is one version of the SecureAnyCloud Hardened Apache Cassandra, FerretDB & NoSQL Database AMIs listing on AWS Marketplace. You select it as the software version when you subscribe and launch. The listing's guide hub describes the other databases in the listing.
The image runs the official XTDB 2.1.0 standalone server directly under systemd on Ubuntu's OpenJDK 21. The server is the xtdb-standalone.jar published in the upstream ghcr.io/xtdb/xtdb:2.1.0 image, verified by SHA-256 at build time. There is no Docker daemon on the instance.
What you get:
- XTDB 2.1.0 (
xtdb.service) with its PostgreSQL wire endpoint on127.0.0.1:5432and its health endpoints on127.0.0.1:8080. - A single-node standalone setup. The transaction log and storage live on local disk under
/var/lib/xtdb. - The superuser
xtdbwith a password unique to this instance, and a TLS certificate generated on the instance's first boot. - A small bitemporal demo table,
employees, with salary history, so you can try time travel straight away. sudo xtdb-psql, a helper that openspsqlto the local database over TLS, using the password from the credentials file.
Default user: ubuntu. Architecture: x86_64. Root volume: 20 GiB gp3.
Security posture
Upstream XTDB 2.1.0 starts with authentication turned off. Its default rule trusts every connection, and the built-in superuser is xtdb with the password xtdb. This image changes that:
- Password required for every connection. XTDB is configured with a single authentication rule,
PASSWORD, for every user and every client address, including connections from the instance itself. There is notrustrule. XTDB 2.1.0 does not apply theremoteAddressfield of an authentication rule, so a "trust localhost only" rule would trust everyone. The image does not use one. - No default password. First boot replaces the upstream
xtdb/xtdblogin with a random password unique to this instance. It writes the password to/root/xtdb-credentials.txt, which only root can read. The change happens in an isolated network namespace, so nothing else can reach the default password.xtdb.servicedoes not start until first boot has proved that the default is refused. - Per-instance TLS certificate. First boot creates this instance's own RSA-3072 key and self-signed certificate, in a keystore with its own random password. XTDB offers TLS to every client. No key ships in the image, so no two instances share one.
- Localhost only. XTDB listens on
127.0.0.1, and the listing's security group opens SSH (22) only. Reach the database through an SSH tunnel, or open it to your VPC, as described in Step 7. - Use
sslmode=require. XTDB sends passwords in PostgreSQL's cleartext-password exchange and, in 2.1.0, cannot refuse a client that does not ask for TLS. The tunnel encrypts that exchange. If you open the port to your VPC, every client must connect withsslmode=require(orverify-full). - No metadata access for the database. The XTDB server and its first-boot service are blocked from reaching the instance metadata service (169.254.169.254 and fd00:ec2::254). Amazon Time Sync (169.254.169.123) still works.
- Sandboxed service. XTDB runs as the unprivileged
xtdbuser with no Linux capabilities,NoNewPrivileges, a read-only view of the operating system (ProtectSystem=strict, writable only under/var/lib/xtdb), a private/tmpand no device access. - IMDSv2 required. The AMI is registered with
ImdsSupport v2.0, so instances launched from it refuse token-less (IMDSv1) metadata requests. - Fails closed. If first boot cannot finish, XTDB stays stopped. First boot tries again on the next boot from a fresh, empty node.
- No secrets in logs. Passwords are never passed on a command line, so they do not appear in
/var/log/auth.logor the journal. - Per-instance identity. SSH host keys, the machine ID and the XTDB node itself are created fresh on every instance. The image ships no data.
Prerequisites
- An AWS account and permission to launch EC2 instances and create security groups.
- An EC2 key pair in the Region you launch in.
- A VPC subnet, with a public IP on the instance if you connect over the internet.
- A PostgreSQL client on your workstation (
psql, DBeaver, or your application's PostgreSQL driver).
Recommended instance type: m5.large. XTDB runs on the JVM and sizes its heap to 40% of the instance's memory. Choose a larger memory size for bigger working sets.
Connecting to your instance
XTDB 2.1.0 on Ubuntu 24.04 runs Ubuntu 24.04 LTS. Connect over SSH as the user ubuntu.
Launch the instance
Option A: AWS Marketplace console
- Open the SecureAnyCloud Hardened Apache Cassandra, FerretDB & NoSQL Database AMIs listing on AWS Marketplace. Choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- When the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace, or Launch from EC2 Console for the full launch wizard. In Version, choose XTDB 2.1.0 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largerecommended; the software charge is $0.04 per vCPU per hour) and your key pair. - Set the security group. The listing's default group allows SSH (22) only. Restrict it to your own IP address, and do not open 5432 or 8080 to the internet.
- Choose your VPC and subnet, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.
Option B: AWS CLI
Run this on your own workstation. Replace the AMI ID with the one shown for your Region on the Marketplace launch page. Replace the VPC, subnet, key pair and CIDR with your own:
SG=$(aws ec2 create-security-group --group-name xtdb \
--description "XTDB" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 run-instances \
--image-id ami-0123456789abcdef0 \
--instance-type m5.large \
--key-name my-key-pair \
--security-group-ids "$SG" \
--subnet-id subnet-0123456789abcdef0 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=xtdb}]'You do not need --metadata-options, because the AMI already requires IMDSv2.
Connect and read your credentials
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>First boot finishes within about a minute of launch. It creates this instance's TLS keystore, changes the xtdb superuser password and checks that the old default is refused. It then loads the demo data and writes the credentials file.
Show the connection details. This command leaves out the password line. Read the password with sudo cat /root/xtdb-credentials.txt and keep it in your secrets manager:
sudo grep -v '^XTDB_PASSWORD=' /root/xtdb-credentials.txt# XTDB 2.1.0 - credentials for THIS instance (generated at first boot; not shared with any other instance)
#
# XTDB speaks the PostgreSQL wire protocol on 127.0.0.1:5432 (loopback only; the security group opens
# SSH only). From your workstation open an SSH tunnel, then connect with any PostgreSQL client:
# ssh -i <your-key.pem> -N -L 5432:127.0.0.1:5432 ubuntu@<instance-public-ip>
# psql "host=127.0.0.1 port=5432 dbname=xtdb user=xtdb sslmode=require"
# On the instance itself:
sudo xtdb-psql XTDB_HOST=127.0.0.1 XTDB_PORT=5432 XTDB_DATABASE=xtdb XTDB_USER=xtdb XTDB_TLS_CERTIFICATE=/etc/xtdb/tls/xtdb-server.crtsudo xtdb-psql reads the password from that file into a temporary, root-only password file for each session. The password never appears on screen or on a command line.
Confirm XTDB is running
Check the services, the Java version, the listeners and the health endpoint:
systemctl is-active xtdb-firstboot.service xtdb.service java -version 2>&1 | head -1 sudo ss -Hltn curl -s -w '\n' http://127.0.0.1:8080/healthz/aliveactive active openjdk version "21.0.12.1" 2026-08-18 LISTEN 0 4096 127.0.0.54:53 0.0.0.0:* LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* LISTEN 0 50 [::ffff:127.0.0.1]:5432 *:* LISTEN 0 50 [::ffff:127.0.0.1]:8080 *:* LISTEN 0 4096 [::]:22 [::]:* Alive.Only SSH listens on a public address. XTDB's PostgreSQL wire endpoint (5432) and its health endpoints (8080: /healthz/alive, /healthz/started, /healthz/ready) listen on localhost. Port 53 on 127.0.0.53 and 127.0.0.54 is Ubuntu's local DNS resolver.
Connect with psql
Open a TLS connection to the local database and print the connection details:
sudo xtdb-psql -c '\conninfo'You are connected to database "xtdb" as user "xtdb" on host "127.0.0.1" at port "5432". SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)Run sudo xtdb-psql with no arguments for an interactive session.
Query across time
The demo table employees records salary changes as new valid-time periods. List every version of every row:
sudo xtdb-psql -c "SELECT _id, name, salary, _valid_from, _valid_to FROM employees FOR ALL VALID_TIME WHERE _id <= 3 ORDER BY _id, _valid_from"_id | name | salary | _valid_from | _valid_to -----+-----------+--------+---------------------------+--------------------------- 1 | Alice Ng | 85000 | 2023-01-01 00:00:00+00:00 | 2024-01-01 00:00:00+00:00 1 | Alice Ng | 95000 | 2024-01-01 00:00:00+00:00 | 2025-01-01 00:00:00+00:00 1 | Alice Ng | 110000 | 2025-01-01 00:00:00+00:00 | 2 | Ben Cole | 70000 | 2023-06-01 00:00:00+00:00 | 2024-06-01 00:00:00+00:00 2 | Ben Cole | 78000 | 2024-06-01 00:00:00+00:00 | 3 | Cara Diaz | 120000 | 2024-03-01 00:00:00+00:00 | (6 rows)Now compare what the table said on 1 July 2023 with what it says today:
sudo xtdb-psql -c "SELECT _id, name, salary FROM employees FOR VALID_TIME AS OF DATE '2023-07-01' ORDER BY _id"
sudo xtdb-psql -c "SELECT _id, name, salary FROM employees WHERE _id <= 3 ORDER BY _id"_id | name | salary -----+----------+-------- 1 | Alice Ng | 85000 2 | Ben Cole | 70000 (2 rows) _id | name | salary -----+-----------+-------- 1 | Alice Ng | 110000 2 | Ben Cole | 78000 3 | Cara Diaz | 120000 (3 rows)Cara Diaz did not exist on 1 July 2023, because her row is valid from March 2024. The time-travel query therefore leaves her out.
Change data and audit it
An UPDATE never overwrites. Change a salary, then look at the row's system-time history. The old value is still there, closed at the moment of the update.
sudo xtdb-psql -c "UPDATE employees SET salary = 125000 WHERE _id = 3"
sudo xtdb-psql -c "SELECT _id, salary, _system_from, _system_to FROM employees FOR ALL SYSTEM_TIME WHERE _id = 3 ORDER BY _system_from"UPDATE 0 _id | salary | _system_from | _system_to -----+--------+----------------------------------+---------------------------------- 3 | 120000 | 2026-10-19 15:53:01.308386+00:00 | 2026-10-19 15:55:28.228996+00:00 3 | 125000 | 2026-10-19 15:55:28.228996+00:00 | (2 rows)XTDB does not report row counts for writes (UPDATE 0, INSERT 0 0). The history query shows that the change took effect. The update also starts a new valid-time period for the row from now, so FOR ALL VALID_TIME now lists both salaries.
Tables need no CREATE TABLE. Inserting a row with an _id creates the table:
sudo xtdb-psql -c "INSERT INTO orders (_id, customer, total) VALUES ('o-1001', 'Acme Ltd', 249.00)"
sudo xtdb-psql -c "SELECT * FROM orders"INSERT 0 0 _id | customer | total --------+----------+------- o-1001 | Acme Ltd | 249.0 (1 row)Connect from your workstation or application
Through an SSH tunnel (recommended)
On your workstation, forward a local port to the instance's localhost-only endpoint. Then connect with any PostgreSQL client, using the password from Step 2:
ssh -i ~/.ssh/my-key-pair.pem -N -L 5432:127.0.0.1:5432 ubuntu@<public-ip> psql "host=127.0.0.1 port=5432 dbname=xtdb user=xtdb sslmode=require"A JDBC application uses jdbc:postgresql://127.0.0.1:5432/xtdb?sslmode=require with user xtdb. To check the server certificate as well, copy /etc/xtdb/tls/xtdb-server.crt to your workstation. Then use sslmode=verify-full sslrootcert=xtdb-server.crt with a host name that the certificate lists. localhost works through the tunnel.
From your VPC
To let application servers in your VPC connect directly:
- Edit
/etc/xtdb/config.yamland changehost: 127.0.0.1underserver:to0.0.0.0(or to the instance's private IP). - Run
sudo systemctl restart xtdb.service. - Add an inbound rule for TCP 5432 to the instance's security group, from your application subnet's CIDR only and never
0.0.0.0/0. - Connect with
sslmode=require(orverify-full) from every client. XTDB 2.1.0 cannot refuse a client that skips TLS, and such a client would send the password unencrypted.
First boot created the certificate for the instance's host name and private IP addresses, so verify-full works against the private IP.
Check the security controls
Start with the configuration. It has localhost listeners, the single PASSWORD rule, and a keystore password that XTDB reads from the root-only /etc/xtdb/xtdb.env instead of from the file:
sudo cat /etc/xtdb/config.yaml# sac XTDB 2.1.0 - single-node standalone. Change server.host only together with the
# security group (open 5432 to your VPC CIDR, never 0.0.0.0/0) and connect with sslmode=require. server: host: 127.0.0.1 port: 5432 ssl: keyStore: /etc/xtdb/tls/xtdb-keystore.jks keyStorePassword: !Env XTDB_KEYSTORE_PASSWORD authn: !UserTable rules: - method: PASSWORD log: !Local path: /var/lib/xtdb/log storage: !Local path: /var/lib/xtdb/buffers healthz: host: 127.0.0.1 port: 8080Next, confirm that the server refuses the upstream default login. This test connects without TLS, on localhost only, so the refusal message is shown. Over TLS, XTDB 2.1.0 reports a wrong password as SSL error: wrong version number.
OUT="$(PGPASSWORD=<set-at-first-boot> psql -X -w "host=127.0.0.1 port=5432 dbname=xtdb user=xtdb sslmode=disable" -c 'SELECT 1' 2>&1)" case "$OUT" in *"password authentication failed for user: xtdb"*) echo "default xtdb/xtdb login: refused (password authentication failed)" ;; *) echo "UNEXPECTED: $OUT"; exit 1 ;; esacdefault xtdb/xtdb login: refused (password authentication failed)Then check that the endpoint serves this instance's own certificate:
openssl s_client -starttls postgres -connect 127.0.0.1:5432 </dev/null 2>/dev/null | openssl x509 -noout -subject -ext subjectAltNamesubject=O = XTDB on this instance, CN = ip-172-31-95-61 X509v3 Subject Alternative Name: DNS:localhost, IP Address:127.0.0.1, DNS:ip-172-31-95-61, DNS:ip-172-31-95-61.ec2.internal, IP Address:172.31.95.61Finally, check the service sandbox and the metadata block. This command sends a metadata request from inside XTDB's own control group. 000 means that the connection was blocked:
systemctl show -p User -p NoNewPrivileges -p ProtectSystem -p IPAddressDeny xtdb.service CODE="$(sudo bash -c 'echo $$ > "/sys/fs/cgroup$(systemctl show -p ControlGroup --value xtdb.service)/cgroup.procs"; curl -s -m 5 -o /dev/null -w "%{http_code}" -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 60" http://169.254.169.254/latest/api/token || true')" echo "metadata request from inside xtdb.service: ${CODE}"; [ "${CODE}" = "000" ]IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32 User=xtdb ProtectSystem=strict NoNewPrivileges=yes metadata request from inside xtdb.service: 000Maintenance
- Change the password. Pipe the statement to
xtdb-psqlon standard input so the new password stays off the command line:printf "ALTER USER xtdb WITH PASSWORD '%s';\n" "$NEW" | sudo xtdb-psql. Then updateXTDB_PASSWORDin/root/xtdb-credentials.txt, which is where the helper reads it. Create additional logins the same way withCREATE USER name WITH PASSWORD '...'. - Your own certificate. To serve a certificate from your own CA, convert your key and certificate to a JKS keystore (
openssl pkcs12 -export, thenkeytool -importkeystore -deststoretype JKS) protected by the password in/etc/xtdb/xtdb.env. Replace/etc/xtdb/tls/xtdb-keystore.jks(owner root, group xtdb, mode 0640) and restartxtdb.service. - Backups. The whole database is the directory
/var/lib/xtdb. For a consistent copy, stop the service (sudo systemctl stop xtdb.service), copy or snapshot the directory (or take an EBS snapshot of the volume), then start the service again. - Memory. The JVM heap is 40% of the instance's memory (
-XX:MaxRAMPercentage=40in/usr/local/sbin/xtdb-server). XTDB also uses off-heap memory for its Arrow buffers, so leave the rest for it. - Updates.
sudo apt update && sudo apt upgradekeeps Ubuntu and OpenJDK 21 patched, and unattended security upgrades are on. The XTDB server is the pinned 2.1.0 jar at/opt/xtdb/xtdb-standalone.jar. - Logs.
journalctl -u xtdb.serviceshows the server log.journalctl -u xtdb-firstboot.serviceshows what first boot did.
Support
SecureAnyCloud supports this image by email at sales@secureanycloud.com. Include the instance ID, the version (XTDB 2.1.0 on Ubuntu 24.04) and what you were doing.
XTDB is open source software licensed under the Mozilla Public License 2.0. The license text is on the instance at /usr/share/doc/xtdb/LICENSE. XTDB is a trademark of its owners. PostgreSQL is a trademark of the PostgreSQL Community Association of Canada. Ubuntu is a trademark of Canonical Ltd. SecureAnyCloud is not affiliated with or endorsed by these organizations.
Deploy on AWS
Launch XTDB 2.1.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com