Databases AWS

BaseX 12.4 on AWS User Guide

| Product: BaseX 12.4 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using BaseX 12.4 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened Apache Cassandra, FerretDB & NoSQL Database AMIs 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.

BaseX is a light, fast native XML database and XQuery 3.1 processor. It stores XML (and JSON, CSV and binary resources) in indexed databases, answers XPath, XQuery and full-text queries, updates documents with the XQuery Update Facility and serves results over HTTP. You work with it through the DBA web admin (databases, an XQuery editor, users, jobs and logs), the REST API, RESTXQ web applications you write in XQuery, and the basexclient command line and language bindings that talk to the BaseX database server.

BaseX's admin user is a superuser: XQuery can read and write files, fetch URLs and run programs. A stock install starts with a random admin password written in clear to its log, and the HTTP server listens on every interface. The cloudimg image changes that:

  • An admin password unique to your instance. First boot sets the BaseX admin password with BaseX's own ALTER PASSWORD command, read from a private temporary file (never a command line or a log), and writes it to the root only file /root/basex-credentials.txt. BaseX stores only salted hashes. The image ships no user file, BaseX refuses to start until first boot has set the password, and the default admin/admin login is refused on the DBA, the REST API and the database server.
  • HTTPS from the first boot. nginx is the only network entry point. It publishes the DBA and the REST API on port 443 with a certificate generated on your instance's first boot; port 80 only redirects to HTTPS. Nothing else BaseX serves is published (the root URL opens the DBA).
  • BaseX on localhost only. The BaseX HTTP server (8984) and the BaseX database server (1984) listen on 127.0.0.1 only. Never open those ports in a security group.
  • XQuery confined. BaseX runs as its own basex user with no capabilities and a read-only system: XQuery can write only the data volume, cannot see home directories, the TLS key, nginx, the system logs or /etc/shadow, and neither BaseX nor nginx can reach the EC2 instance metadata endpoint, so an XQuery http:send-request or fetch:text cannot read instance credentials.
  • Fixed host name and real client addresses. Requests for a host name the instance does not serve are dropped, nginx logs the real client address (client supplied X-Forwarded-For headers are overwritten), the DBA session cookie is HTTPS only, and cross site browser requests that change state are refused. DBA logins are rate limited.
  • Your data on its own disk. Databases, the user file, the BaseX logs and the XQuery package repository live on a dedicated 20 GiB EBS volume mounted at /var/lib/basex, which you can resize independently of the root volume.
  • Verified download. The BaseX 12.4 release zip was checked against a pinned SHA-256, and its BaseX.jar is byte-identical to the org.basex:basex:12.4 artifact whose SHA-256 BaseX publishes on Maven Central.
  • Key only SSH. Password logins, root logins and X11 forwarding are switched off.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: BaseX 12.4 (/opt/basex, BSD-3-Clause), Ubuntu's OpenJDK 21 runtime, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

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

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
  • A security group that allows TCP 22 (SSH) from your own networks and TCP 443 (DBA and REST) from the networks and applications that use BaseX. TCP 80 only redirects to HTTPS and is optional. Do not open 1984 or 8984.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick BaseX 12.4 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above. The AMI brings its own 20 GiB data volume for BaseX; increase its size on the launch page if you expect more data (Step 9).

With the AWS CLI (replace the values in angle brackets):

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> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=basex}]'

Step 2: Connect to your instance

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

Step 3: Check the services

First boot generates the certificate and the admin password, then BaseX and nginx start. BaseX is ready a few seconds after boot:

systemctl is-active basex nginx basex-firstboot
sudo test -f /var/lib/cloudimg/basex-firstboot.done && echo FIRSTBOOT_DONE
java -cp /opt/basex/BaseX.jar org.basex.BaseX -h 2>&1 | head -1
java -version 2>&1 | head -1
active
active
active
FIRSTBOOT_DONE
BaseX 12.4 [Standalone]
openjdk version "21.0.12.1" 2026-08-18

Only SSH and nginx listen on public addresses; the BaseX HTTP server (8984) and database server (1984) are on loopback:

sudo ss -Hlntp | awk '{print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:1984 users:(("java"))
127.0.0.1:8984 users:(("java"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))

Step 4: Get your URLs and password

The URLs and the user name are in the root only credentials file:

sudo grep -E '^BASEX_(DBA_URL|REST_URL|ADMIN_USER)=' /root/basex-credentials.txt
BASEX_DBA_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/dba
BASEX_REST_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/rest
BASEX_ADMIN_USER=admin

The password is the BASEX_ADMIN_PASSWORD line of the same file: sudo grep '^BASEX_ADMIN_PASSWORD=' /root/basex-credentials.txt. Store it in your password manager; it is unique to this instance and admin is the BaseX superuser.

The URLs use the instance's public DNS name from EC2 instance metadata (IMDSv2), or its public or private IPv4 address when it has no public name. The certificate names that host plus the instance's public and private IP addresses:

openssl x509 -in /etc/ssl/basex/basex.crt -noout -subject -ext subjectAltName -enddate
subject=CN = ec2-<public-ip-dashed>.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name: 
    DNS:ec2-<public-ip-dashed>.compute-1.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
notAfter=<date about 27 months after first boot>

Without the password the REST API answers 401, the default admin/admin login is refused, the DBA sends you to its login page, and port 80 redirects to HTTPS. The commands below read the password from the credentials file into a shell variable and pass it to curl through a file descriptor, so it never appears in the process list or your shell history:

H=$(sudo sed -n 's#^BASEX_DBA_URL=https://\([^/]*\)/.*#\1#p' /root/basex-credentials.txt)
PW=$(sudo sed -n 's/^BASEX_ADMIN_PASSWORD=//p' /root/basex-credentials.txt)
R=(--cacert /etc/ssl/basex/basex.crt --resolve "$H:443:127.0.0.1")
curl -s -o /dev/null -w 'http://             -> %{http_code} %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/dba"
curl -s -o /dev/null -w 'REST, no login      -> HTTP %{http_code}\n' "${R[@]}" "https://$H/rest"
curl -s -o /dev/null -w 'REST, admin:admin   -> HTTP %{http_code}\n' "${R[@]}" -K <(printf 'user = "admin:admin"\n') "https://$H/rest"
curl -s -o /dev/null -w 'REST, your password -> HTTP %{http_code}\n' "${R[@]}" -K <(printf 'user = "admin:%s"\n' "$PW") "https://$H/rest"
curl -s -o /dev/null -w 'DBA, no login       -> %{http_code} %{redirect_url}\n' "${R[@]}" "https://$H/dba/databases"
curl -s -w ' <- /healthz (no login) HTTP %{http_code}\n' "${R[@]}" "https://$H/healthz"
http://             -> 301 https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/dba
REST, no login      -> HTTP 401
REST, admin:admin   -> HTTP 401
REST, your password -> HTTP 200
DBA, no login       -> 302 https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/dba/login?_page=databases
ok
 <- /healthz (no login) HTTP 200

--resolve sends the request to this instance while keeping the real host name, so the certificate check passes. From another machine, drop --resolve and use the URL directly.

Step 5: Create a database and query it over REST

The REST API takes the same user and password with HTTP Basic authentication over HTTPS. This example creates a library database from an XML document with PUT, runs two XQuery expressions against it, lists the databases and returns a JSON summary. The small bx function reads the password as above and hands it to curl through a file descriptor:

H=$(sudo sed -n 's#^BASEX_REST_URL=https://\([^/]*\)/.*#\1#p' /root/basex-credentials.txt)
PW=$(sudo sed -n 's/^BASEX_ADMIN_PASSWORD=//p' /root/basex-credentials.txt)
bx() { curl -s --cacert /etc/ssl/basex/basex.crt --resolve "$H:443:127.0.0.1" -K <(printf 'user = "admin:%s"\n' "$PW") "$@"; echo; }
bx -X PUT -H 'Content-Type: application/xml' --data-binary '<library>
  <book id="b1"><title>XQuery</title><author>Priscilla Walmsley</author><year>2015</year></book>
  <book id="b2"><title>XML in a Nutshell</title><author>Elliotte Rusty Harold</author><year>2004</year></book>
  <book id="b3"><title>Designing Data-Intensive Applications</title><author>Martin Kleppmann</author><year>2017</year></book>
</library>' "https://$H/rest/library"
bx --get --data-urlencode 'query=for $b in //book order by xs:integer($b/year) descending return $b/title/string()' "https://$H/rest/library"
bx --get --data-urlencode 'query=//book[xs:integer(year) gt 2010]/author/string()' "https://$H/rest/library"
bx "https://$H/rest"
bx --get --data-urlencode 'query=json:serialize(map { "books": count(//book), "newest": max(//book/year ! xs:integer(.)) })' "https://$H/rest/library"
Database 'library' created in 7.06 ms.

Designing Data-Intensive Applications
XQuery
XML in a Nutshell
Priscilla Walmsley
Martin Kleppmann
<databases xmlns="http://basex.org/rest"><database resources="1" size="5533">library</database></databases>
{"books":3,"newest":2017}

GET /rest/<database> without a query returns the stored documents, DELETE /rest/<database> drops a database, and POST takes a query or command document. See the BaseX documentation on the REST API for the full set of operations.

Step 6: Use the DBA web admin

Open the BASEX_DBA_URL from Step 4 (https://<your-host>/dba) in a browser. The certificate is self signed, so the browser warns once; accept it, or install your own certificate (Step 10). Sign in as admin with the password from the credentials file:

The BaseX Database Administration sign in page served by the cloudimg image, asking for the admin name and password

Databases lists every database with its resource count, size and last change, and creates, optimizes, drops, backs up and restores them:

The BaseX DBA Databases page listing the library database with one resource of 5533 bytes, with the create, optimize, drop, back up and restore actions and the general backups panel

Click a database to see its resources, its backups and its properties and indexes:

The BaseX DBA page for the library database, showing the library.xml resource and the database properties: 30 nodes, one document, and the text and attribute indexes switched on

Editor runs XQuery against your databases and shows the result next to the query (Ctrl-Enter runs it):

The BaseX DBA XQuery editor running a FLWOR query over the library database, with the three books returned newest first in the result panel

Users creates further users and sets their permissions (read, write, create or admin), and Logs shows every request with its user, address and timing. Give applications their own user with the least permission they need rather than the admin superuser.

Step 7: Use basexclient on the instance

The BaseX command line client is on every login's PATH. It connects to the BaseX database server on 127.0.0.1:1984 and asks for the password; when standard input is not a terminal it reads the password from there, so you can pipe it straight from the credentials file:

sudo sed -n 's/^BASEX_ADMIN_PASSWORD=//p' /root/basex-credentials.txt | /opt/basex/bin/basexclient -U admin -c 'XQUERY count(db:get("library")//book)'; echo
Password: 3

Run basexclient -U admin without -c for an interactive session; it prompts for the password. Client libraries for Java, Python, Node.js and other languages speak the same protocol: run them on the instance, or forward the port over SSH from your workstation and connect to localhost:1984:

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

Never open 1984 or 8984 in a security group. For applications on other hosts use the REST API over HTTPS (Step 5).

Step 8: XQuery confinement and instance metadata

XQuery functions run inside the BaseX service. proc:system runs programs as the unprivileged basex user, the file functions see a read-only system with the secrets hidden, and requests to the instance metadata address are dropped:

H=$(sudo sed -n 's#^BASEX_REST_URL=https://\([^/]*\)/.*#\1#p' /root/basex-credentials.txt)
PW=$(sudo sed -n 's/^BASEX_ADMIN_PASSWORD=//p' /root/basex-credentials.txt)
xq() { curl -s --cacert /etc/ssl/basex/basex.crt --resolve "$H:443:127.0.0.1" -K <(printf 'user = "admin:%s"\n' "$PW") --get --data-urlencode "query=$1" "https://$H/rest"; echo; }
xq 'normalize-space(proc:system("id", "-un"))'
xq 'try { file:read-text("/etc/shadow") } catch * { "refused: " || $err:description }'
xq 'try { file:write-text("/etc/cloudimg-test", "x") } catch * { "refused: " || $err:description }'
xq 'try { http:send-request(<http:request method="put" timeout="5"><http:header name="X-aws-ec2-metadata-token-ttl-seconds" value="60"/></http:request>, "http://169.254.169.254/latest/api/token")[1]/@status/string() } catch * { "refused: " || $err:description }'
basex
refused: Access to '/etc/shadow' is denied.
refused: Resource "/etc/cloudimg-test (Read-only file system)" not found.
refused: HTTP connect timed out

The service settings behind it:

systemctl show basex -p User -p NoNewPrivileges -p ProtectSystem -p ProtectHome -p PrivateTmp -p IPAddressDeny
systemctl show nginx -p IPAddressDeny
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32
User=basex
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

Step 9: Storage, memory and logs

BaseX keeps its databases, the user file users.xml and its logs under /var/lib/basex/data, and the XQuery package repository under /var/lib/basex/repo, on the dedicated EBS volume. The JVM runs with a 2 GB heap:

df -h /var/lib/basex | tail -1 | awk '{print $2" total, "$4" free on "$6}'
sudo grep -E '^(DBPATH|REPOPATH|SERVERHOST|SERVERPORT) ' /opt/basex/.basex
grep -oE -- '-Xmx[0-9a-z]+' /usr/local/lib/cloudimg/basex-java
20G total, 19G free on /var/lib/basex
DBPATH = /var/lib/basex/data
REPOPATH = /var/lib/basex/repo
SERVERPORT = 1984
SERVERHOST = 127.0.0.1
-Xmx2g
  • Heap. On a larger instance raise -Xmx2g in /usr/local/lib/cloudimg/basex-java (about half of the instance's memory is a good start), then sudo systemctl restart basex.
  • Disk. To grow the volume, modify it in the EC2 console (Volumes, Modify volume) and extend the file system online: sudo resize2fs "$(findmnt -no SOURCE /var/lib/basex)".
  • Backups. Create database backups in the DBA (Databases, Back up) or with the CREATE BACKUP command; they are written next to the databases on the data volume. For a full copy, stop BaseX (sudo systemctl stop basex) and take an EBS snapshot of the data volume.
  • Logs. BaseX writes one log per day to /var/lib/basex/data/.logs (also under Logs in the DBA); journalctl -u basex shows the service output and journalctl -u basex-firstboot what first boot did.

Step 10: Use your own DNS name or certificate

The served host name is fixed by configuration, never taken from a request. To serve BaseX under your own DNS name, point the name at the instance and run the helper, which records the name, regenerates the self signed certificate for it, updates the credentials file and reloads nginx:

sudo basex-set-hostname <your-dns-name>

Run the same command after a stop and start if the instance's public DNS name changed (an Elastic IP avoids that), because a request for a name the instance does not serve is dropped.

To use a certificate from your own CA or a public CA instead, replace /etc/ssl/basex/basex.crt (full chain) and /etc/ssl/basex/basex.key (mode 0600, owner root), then run sudo nginx -t && sudo systemctl reload nginx.

Step 11: Security notes

SSH accepts keys only: passwords, root logins and X11 forwarding are switched off in /etc/ssh/sshd_config.d/00-cloudimg.conf, which sorts before every other SSH setting. Check the effective values:

sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin|x11forwarding|pubkeyauthentication) '
permitrootlogin no
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no
x11forwarding no

Other points:

  • To change the admin password, sign in to the DBA, open Users, then admin, and set a new password (or run ALTER PASSWORD admin in basexclient), then update /root/basex-credentials.txt.
  • The admin user can read and write files on the data volume, query other web services and run programs as the basex user through XQuery. Use separate users with read or write permission for applications.
  • nginx logs the real client address of every request; X-Forwarded-For headers sent by clients are ignored. The AMI requires IMDSv2. Restrict port 443 to the networks that use BaseX.
  • Bundled libraries. BaseX 12.4 ships Jetty 12.1.8, which a scanner reports as affected by CVE-2026-10050 (high: a DIGEST authentication bypass in Jetty's security module), CVE-2026-6790 (Host header validation), CVE-2026-8384 (a path confusion in Jetty's static file handling) and CVE-2026-10051 (HTTP/1.1 trailers kept across connections), all fixed in Jetty 12.1.10. BaseX does not use Jetty's authenticators (it checks the HTTP Basic credentials itself, and this image uses Basic authentication only), and Jetty is reached only through nginx, which sends a fixed host name, forwards a normalised path for /dba and /rest only, and re-frames each request. 12.4 is the newest BaseX release; cloudimg updates the image when BaseX ships a release with the newer Jetty.

When you have finished with the example database from Step 5, drop it:

H=$(sudo sed -n 's#^BASEX_REST_URL=https://\([^/]*\)/.*#\1#p' /root/basex-credentials.txt)
PW=$(sudo sed -n 's/^BASEX_ADMIN_PASSWORD=//p' /root/basex-credentials.txt)
curl -s --cacert /etc/ssl/basex/basex.crt --resolve "$H:443:127.0.0.1" -K <(printf 'user = "admin:%s"\n' "$PW") -X DELETE "https://$H/rest/library"; echo
Database 'library' was dropped.

Support and licensing

cloudimg supports this image by email at support@cloudimg.co.uk. Include the instance ID, the version (BaseX 12.4 on Ubuntu 24.04) and what you were doing.

BaseX is open source software under the BSD 3-Clause licence; the licence is on the instance at /opt/basex/LICENSE and the licences of the bundled libraries at /opt/basex/lib/LICENSES. There is no per user or per CPU licence fee. The AMI price covers cloudimg's hardening, first boot setup and support.

BaseX is a trademark of its owner. Ubuntu is a trademark of Canonical Ltd. Java is a trademark of Oracle and/or its affiliates. Eclipse Jetty is a trademark of the Eclipse Foundation. cloudimg is not affiliated with or endorsed by these organisations.