Deployment guide

BaseX

Overview

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, and the basexclient command-line client and language bindings that talk to the BaseX database server.

This SecureAnyCloud image runs BaseX 12.4 on Ubuntu 24.04 LTS on Microsoft Azure, and this guide covers deploying and using it. 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 text to its log, and the HTTP server listens on every interface. The SecureAnyCloud image changes both defaults.

What you get:

  • An admin password unique to your VM. First boot sets the BaseX admin password with BaseX's own ALTER PASSWORD command, reading it from a private temporary file (never a command line or a log). It writes the password to the root-only file /root/basex-info.txt, and BaseX stores only salted hashes. The image ships no user file, and BaseX refuses to start until first boot has set the password. 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 VM's first boot, and port 80 only redirects to HTTPS. Nothing else that BaseX serves is published. The root URL opens the DBA, and the RESTXQ samples, the WebSocket chat demo and the static files are not reachable.
  • 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 network 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 disk, and cannot see home directories, the TLS key, nginx, the system logs or /etc/shadow. Neither BaseX, nginx nor the first-boot service can reach the Azure metadata service or the wire server, so an XQuery http:send-request or fetch:text cannot read anything about your VM or subscription.
  • Fixed host name and real client addresses. Requests for a host name that the VM does not serve are dropped. nginx logs the real client address: client-supplied X-Forwarded-For headers are overwritten, and BaseX always receives a fixed host name. The DBA session cookie is HTTPS-only, cross-site browser requests that change state are refused, and 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 30 GiB data disk mounted at /var/lib/basex, which you can resize independently of the OS disk.
  • A verified download. The BaseX 12.4 release zip was checked against a pinned SHA-256. Its BaseX.jar is byte-identical to the org.basex:basex:12.4 artifact whose SHA-256 BaseX publishes on Maven Central.
  • SSH as you chose it. Root logins, empty passwords and X11 forwarding are switched off. Key or password sign-in follows the choice you make when you create the VM.
  • A fully patched base. Ubuntu 24.04 LTS is patched at build time, with unattended security upgrades switched on.
  • The full stack 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 Azure Linux agent.
  • Default user: azureuser (or the name you choose)
  • Architecture: x64, Gen2
  • Recommended size: Standard_B2s for evaluation, Standard_D4s_v5 or larger for production

Prerequisites

  • An Azure subscription, an SSH key pair (or a password), and a virtual network.
  • A network 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.
1

Deploy from the Azure Portal

Search the Azure Marketplace for BaseX on Ubuntu 24.04 and choose Create. Pick your size, your authentication type and a network security group as described in Prerequisites.

The image brings its own 30 GiB data disk for BaseX. You can grow it later (Step 10).

2

Deploy from the Azure CLI

RG="basex-prod"; LOCATION="eastus"; VM_NAME="basex-01" IMAGE_URN="sac1647283583153:basex-ubuntu-24-04:default:latest" az group create --name "$RG" --location "$LOCATION" az network nsg create -g "$RG" --name basex-nsg az network nsg rule create -g "$RG" --nsg-name basex-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name basex-nsg --name allow-https --priority 110 \
  --source-address-prefixes "<your-client-cidr>" --destination-port-ranges 443 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn "$IMAGE_URN"
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --size Standard_B2s --admin-username azureuser --ssh-key-values ~/.ssh/id_rsa.pub \
  --nsg basex-nsg --public-ip-sku Standard
3

Connect to your VM

ssh azureuser@<vm-public-ip>
4

Check the services

On first boot, the VM records its public IP address and generates the certificate and the admin password. Then BaseX and nginx start. BaseX is ready a few seconds after boot:

systemctl is-active sac-public-ip basex-firstboot basexhttp nginx
sudo test -f /var/lib/sac/data/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 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"))
5

Get your URLs and password

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

sudo grep -E '^BASEX_(DBA_URL|REST_URL|ADMIN_USERNAME)=' /root/basex-info.txt
BASEX_DBA_URL=https://<vm-public-ip>/dba BASEX_REST_URL=https://<vm-public-ip>/rest BASEX_ADMIN_USERNAME=admin

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

The URLs use the VM's public IP address, or its private address when it has none. The certificate names that address plus the VM's private address and loopback:

openssl x509 -in /etc/nginx/tls/basex.crt -noout -subject -ext subjectAltName -enddate
subject=CN = <vm-public-ip>, O = sac X509v3 Subject Alternative Name: IP Address:<vm-public-ip>, IP Address:<vm-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 following commands read the password from the info 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-info.txt)
PW=$(sudo sed -n 's/^BASEX_ADMIN_PASSWORD=//p' /root/basex-info.txt) R=(--cacert /etc/nginx/tls/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://<vm-public-ip>/dba REST, no login -> HTTP 401 REST, admin:admin -> HTTP 401 REST, your password -> HTTP 200 DBA, no login -> 302 https://<vm-public-ip>/dba/login?_page=databases ok <- /healthz (no login) HTTP 200

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

6

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-info.txt)
PW=$(sudo sed -n 's/^BASEX_ADMIN_PASSWORD=//p' /root/basex-info.txt) bx() { curl -s --cacert /etc/nginx/tls/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 49.59 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. For the full set of operations, see the BaseX documentation on the REST API.

7

Use the DBA web admin

In a browser, open the BASEX_DBA_URL from Step 5 (https://<vm-public-ip>/dba). The certificate is self-signed, so the browser warns you once. Accept the warning, or install your own certificate (Step 11). Sign in as admin with the password from the info file:

The Databases page lists every database with its resource count, size and last change. You can create, optimize, drop, back up and restore databases from this page:

The Editor runs XQuery against your databases and shows the result next to the query. Press Ctrl-Enter to run a query:

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

The Users page creates further users and sets their permissions (read, write, create or admin). The Logs page 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.

8

Use basexclient on the VM

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, the client reads the password from there, so you can pipe it straight from the info file:

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

For an interactive session, run basexclient -U admin without -c. The client prompts for the password.

Client libraries for Java, Python, Node.js and other languages speak the same protocol. Run them on the VM, or forward the port over SSH from your workstation and connect to localhost:1984:

ssh -N -L 1984:127.0.0.1:1984 azureuser@<vm-public-ip>

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

9

XQuery confinement and the Azure metadata service

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

H=$(sudo sed -n 's#^BASEX_REST_URL=https://\([^/]*\)/.*#\1#p' /root/basex-info.txt)
PW=$(sudo sed -n 's/^BASEX_ADMIN_PASSWORD=//p' /root/basex-info.txt) xq() { curl -s --cacert /etc/nginx/tls/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("/var/lib/sac/config/sac-test", "x") } catch * { "refused: " || $err:description }' xq 'try { http:send-request(<http:request method="get" timeout="5"><http:header name="Metadata" value="true"/></http:request>, "http://169.254.169.254/metadata/instance?api-version=2021-02-01")[1]/@status/string() } catch * { "refused: " || $err:description }'
basex refused: Access to '/etc/shadow' is denied. refused: Resource "/var/lib/sac/config/sac-test (Read-only file system)" not found. refused: HTTP connect timed out

These service settings enforce the confinement. BaseX may write only /var/lib/basex. The metadata service and the wire server are denied to BaseX, nginx and the first-boot service, while the Azure Linux agent keeps its wire server access:

systemctl show basexhttp -p User -p NoNewPrivileges -p ProtectSystem -p ProtectHome -p PrivateTmp -p ReadWritePaths -p IPAddressDeny
systemctl show nginx basex-firstboot -p Id -p IPAddressDeny
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 User=basex ReadWritePaths=/var/lib/basex PrivateTmp=yes ProtectHome=yes ProtectSystem=strict NoNewPrivileges=yes IPAddressDeny=169.254.169.254/32 168.63.129.16/32 Id=nginx.service IPAddressDeny=169.254.169.254/32 168.63.129.16/32 Id=basex-firstboot.service
10

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. Both locations are on the dedicated data disk. The JVM runs with a 1.5 GB heap, sized for a Standard_B2s:

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/sac/basex-java
30G total, 28G free on /var/lib/basex DBPATH = /var/lib/basex/data REPOPATH = /var/lib/basex/repo SERVERPORT = 1984 SERVERHOST = 127.0.0.1 -Xmx1500m
  • Heap. On a larger VM, raise -Xmx1500m in /usr/local/lib/sac/basex-java. About half of the VM's memory is a good starting point. Then run sudo systemctl restart basexhttp.
  • Disk. To grow the data disk, stop the VM (deallocate) and resize the disk in the Azure portal (Disks, Size + performance). Then start the VM 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 disk. For a full copy, stop BaseX (sudo systemctl stop basexhttp) and take an Azure snapshot of the data disk.
  • Logs. BaseX writes one log per day to /var/lib/basex/data/.logs, which is also visible under Logs in the DBA. journalctl -u basexhttp shows the service output, and journalctl -u basex-firstboot shows what first boot did.
11

Use your own DNS name or certificate

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

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

If you use the VM's public IP address and it changes (for example, a dynamic IP after a deallocate), run the same command with the new address, because a request for a name that the VM does not serve is dropped. A static public IP avoids this.

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

12

Security notes

Root logins, empty passwords and X11 forwarding are switched off in /etc/ssh/sshd_config.d/00-sac.conf, which sorts before every other SSH setting. Key or password sign-in is not pinned there. It follows the authentication type you chose when you created the VM. Check the effective values:

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

Also note the following points:

  • Admin password: To change it, sign in to the DBA, open Users, then admin, and set a new password. Alternatively, run ALTER PASSWORD admin in basexclient. Then update /root/basex-info.txt.
  • Superuser reach: The admin user can read and write files on the data disk, query other web services and run programs as the basex user through XQuery. Use separate users with read or write permission for applications.
  • Client addresses: nginx logs the real client address of every request, and X-Forwarded-For headers sent by clients are ignored. Restrict port 443 in your network security group 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 four are 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. 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, and SecureAnyCloud updates the image when BaseX ships a release with the newer Jetty.

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

H=$(sudo sed -n 's#^BASEX_REST_URL=https://\([^/]*\)/.*#\1#p' /root/basex-info.txt)
PW=$(sudo sed -n 's/^BASEX_ADMIN_PASSWORD=//p' /root/basex-info.txt)
curl -s --cacert /etc/nginx/tls/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

Every SecureAnyCloud deployment includes 24/7 support with a 24-hour response SLA. Contact sales@secureanycloud.com with the VM name, the version (BaseX 12.4 on Ubuntu 24.04) and a description of what you were doing.

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

This is a repackaged open source software product with additional charges for SecureAnyCloud support services. 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. SecureAnyCloud is not affiliated with or endorsed by these organisations.

Deploy on Azure

Launch BaseX 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

Deploy on Azure