eXist-db 6.4.1 on AWS User Guide
Overview
This guide covers launching and using eXist-db 6.4.1 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.
eXist-db is an open source native XML database. It stores XML, JSON and binary documents in a hierarchy of collections, indexes them (structural, range, full-text and n-gram indexes) and queries them with XQuery 3.1, XPath and XSLT. You work with it through the Dashboard and the eXide XQuery IDE in a browser, the REST API, WebDAV (editors such as oXygen, or any WebDAV client) and XML-RPC, which eXist's own Java admin client and language bindings use.
A stock eXist-db database opens with an admin account that has an empty password, a guest account whose password is guest, and an account for each bundled application whose password is its own name. Anyone who can reach the server can also run XQuery anonymously. The cloudimg image changes that:
- No default login, a password unique to your instance. The image ships no database at all. First boot creates the database with eXist's client in embedded mode (no network listener is open yet), sets the
adminpassword from a private temporary file (never a command line, a query text or a log) and writes it to the root only file/root/existdb-credentials.txt. Theguestaccount and the bundled applications' accounts are disabled (and re-keyed to random values nobody knows). eXist-db refuses to start until first boot has finished, and the emptyadminpassword andguest/guestare refused on the Dashboard, the REST API, WebDAV, XML-RPC and eXist's Java admin client. - No anonymous queries. nginx refuses REST, WebDAV and XML-RPC requests that carry no credentials, XML-RPC never falls back to the guest account, and eXide only runs queries for a signed-in user. Anonymous visitors see the Dashboard sign-in page and the documentation.
- HTTPS from the first boot. nginx is the only network entry point. It publishes eXist on port 443 with a certificate generated on your instance's first boot; port 80 only redirects to HTTPS. Only the Dashboard and its applications, the REST API, WebDAV and XML-RPC are published.
- eXist-db on localhost only. eXist's web server (Jetty) listens on
127.0.0.1:8080, its HTTPS connector and the distribution's sample keystore are removed, and the JMX status servlet is switched off. Never open port 8080 in a security group. - XQuery confined. eXist-db runs as its own
existdbuser with no capabilities and a read-only system. XQuery can write only the data volume and cannot read the TLS key, nginx, the system logs, the credentials file or/etc/shadow. Thefile,process,sql,jndi,mail,contentextractionandxslfoXQuery modules are switched off, and neither eXist nor nginx can reach the EC2 instance metadata endpoint, so an XQueryhttp:send-requestcannot 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-Forheaders are overwritten), session cookies are HTTPS only, cross site browser requests that change state are refused and Dashboard sign-ins are rate limited. - Your data on its own disk. The database, its journal and indexes, the package repository and the eXist logs live on a dedicated 20 GiB EBS volume mounted at
/var/lib/existdb, which you can resize independently of the root volume. - Verified download. The eXist-db 6.4.1 release was checked against a pinned SHA-256 that matches the digest published for the eXist release, and its
exist-corelibrary carries exactly the classes of the PGP-signedorg.exist-db:exist-core:6.4.1artifact 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: eXist-db 6.4.1 (/opt/existdb, LGPL-2.1) with the Dashboard, eXide, the eXist documentation and the XQuery function documentation, Ubuntu's OpenJDK 17 runtime, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. The Monex monitoring application is not pre-installed (Step 11).
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 (Dashboard, REST, WebDAV and XML-RPC) from the networks and applications that use eXist-db. TCP 80 only redirects to HTTPS and is optional. Do not open 8080.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick eXist-db 6.4.1 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 eXist-db; increase its size on the launch page if you expect more data (Step 10).
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=existdb}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| eXist-db 6.4.1 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot generates the certificate, creates the database, deploys the bundled applications and sets the admin password, then eXist-db and nginx start. Allow about a minute after the instance reaches the running state:
systemctl is-active existdb nginx exist-db-firstboot
sudo test -f /var/lib/cloudimg/exist-db-firstboot.done && echo FIRSTBOOT_DONE
ls /opt/existdb/lib/exist-core-*.jar
java -version 2>&1 | head -1
active
active
active
FIRSTBOOT_DONE
/opt/existdb/lib/exist-core-6.4.1.jar
openjdk version "17.0.20.1" 2026-08-18
Only SSH and nginx listen on public addresses; eXist-db (Jetty, 8080) is 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:8080 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 '^EXISTDB_(URL|DASHBOARD_URL|REST_URL|WEBDAV_URL|XMLRPC_URL|ADMIN_USER)=' /root/existdb-credentials.txt
EXISTDB_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/exist/
EXISTDB_DASHBOARD_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/exist/apps/dashboard/
EXISTDB_REST_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/exist/rest/db
EXISTDB_WEBDAV_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/exist/webdav/db/
EXISTDB_XMLRPC_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/exist/xmlrpc
EXISTDB_ADMIN_USER=admin
The password is the EXISTDB_ADMIN_PASSWORD line of the same file: sudo grep '^EXISTDB_ADMIN_PASSWORD=' /root/existdb-credentials.txt. Store it in your password manager; it is unique to this instance and admin is the eXist-db superuser (a member of the dba group).
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/existdb/existdb.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 empty admin password and guest/guest are refused, the Dashboard shows its sign-in 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#^EXISTDB_URL=https://\([^/]*\)/.*#\1#p' /root/existdb-credentials.txt)
PW=$(sudo sed -n 's/^EXISTDB_ADMIN_PASSWORD=//p' /root/existdb-credentials.txt)
R=(--cacert /etc/ssl/existdb/existdb.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/exist/"
curl -s -o /dev/null -w 'REST, no login -> HTTP %{http_code}\n' "${R[@]}" "https://$H/exist/rest/db"
curl -s -o /dev/null -w 'REST, admin, empty pw -> HTTP %{http_code}\n' "${R[@]}" -K <(printf 'user = "admin:"\n') "https://$H/exist/rest/db"
curl -s -o /dev/null -w 'REST, guest:guest -> HTTP %{http_code}\n' "${R[@]}" -K <(printf 'user = "guest:guest"\n') "https://$H/exist/rest/db"
curl -s -o /dev/null -w 'REST, your password -> HTTP %{http_code}\n' "${R[@]}" -K <(printf 'user = "admin:%s"\n' "$PW") "https://$H/exist/rest/db"
curl -s -o /dev/null -w 'Dashboard, no login -> HTTP %{http_code}\n' "${R[@]}" "https://$H/exist/apps/dashboard/index.html"
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/exist/
REST, no login -> HTTP 401
REST, admin, empty pw -> HTTP 401
REST, guest:guest -> HTTP 401
REST, your password -> HTTP 200
Dashboard, no login -> HTTP 200
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. The Dashboard answers 200 without a login because it shows its sign-in page.
Step 5: Store and query documents over REST
The REST API takes the same user and password with HTTP Basic authentication over HTTPS. This example stores an XML document in a new /db/library collection with PUT, runs three XQuery expressions against the collection (sent in the body of a POST, so the query never appears in a URL) and reads one value with a query on the document's own URL. The small ex function reads the password as above and hands it to curl through a file descriptor:
H=$(sudo sed -n 's#^EXISTDB_URL=https://\([^/]*\)/.*#\1#p' /root/existdb-credentials.txt)
PW=$(sudo sed -n 's/^EXISTDB_ADMIN_PASSWORD=//p' /root/existdb-credentials.txt)
ex() { curl -s --cacert /etc/ssl/existdb/existdb.crt --resolve "$H:443:127.0.0.1" -K <(printf 'user = "admin:%s"\n' "$PW") "$@"; echo; }
xq() { printf '<query xmlns="http://exist.sourceforge.net/NS/exist" wrap="no"><text><![CDATA[%s]]></text></query>' "$1" | ex -H 'Content-Type: application/xml' --data-binary @- "https://$H/exist/rest/db"; }
ex -o /dev/null -w 'store /db/library/books.xml -> HTTP %{http_code}' -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/exist/rest/db/library/books.xml"
xq 'string-join(for $b in collection("/db/library")//book order by xs:integer($b/year) descending return $b/title/string(), codepoints-to-string(10))'
xq 'string-join(collection("/db/library")//book[xs:integer(year) gt 2010]/author, ", ")'
xq 'serialize(map { "books": count(collection("/db/library")//book), "newest": max(collection("/db/library")//year ! xs:integer(.)) }, map { "method": "json" })'
ex "https://$H/exist/rest/db/library/books.xml?_query=//book%5B@id='b2'%5D/title&_wrap=no"
store /db/library/books.xml -> HTTP 201
Designing Data-Intensive Applications
XQuery
XML in a Nutshell
Priscilla Walmsley, Martin Kleppmann
{"books":3,"newest":2017}
<title>XML in a Nutshell</title>
GET on a collection or document URL returns it, DELETE removes it, and stored XQuery modules (.xq, .xql) run when you GET them. Keep passwords and other secrets out of query strings: eXist's web server logs every request line, query string included, to /var/lib/existdb/logs/<date>.request.log. See the eXist-db documentation on the REST interface for the full set of operations.
Step 6: Use the Dashboard and eXide
Open the EXISTDB_DASHBOARD_URL from Step 4 (https://<your-host>/exist/apps/dashboard/) in a browser. The certificate is self signed, so the browser warns once; accept it, or install your own certificate (Step 12). Anonymous visitors see the launcher with the documentation and a Login button:

Choose Login and sign in as admin with the password from the credentials file. The Dashboard then adds the Package Manager, User Manager, Backup and Settings:

Open eXide - XQuery IDE from the launcher (it uses your Dashboard session). Write a query, choose Eval and the results appear below the editor. Here a FLWOR expression lists the books stored in Step 5, newest first:

eXide on this image runs queries only for signed-in users; an anonymous visitor is sent to its sign-in page. The User Manager lists the accounts and creates new users and groups:

Give applications their own user with permissions on just the collections they need rather than the admin superuser.
Step 7: WebDAV, XML-RPC and the Java admin client
WebDAV serves the database at EXISTDB_WEBDAV_URL (https://<your-host>/exist/webdav/db/): add it as a network location in your editor (for example oXygen XML Editor's eXist-db or WebDAV connection) with the admin user or one of your own. XML-RPC at EXISTDB_XMLRPC_URL is the protocol eXist's Java admin client and the XML:DB API use. Both need credentials; neither accepts the empty admin password or guest:
H=$(sudo sed -n 's#^EXISTDB_URL=https://\([^/]*\)/.*#\1#p' /root/existdb-credentials.txt)
PW=$(sudo sed -n 's/^EXISTDB_ADMIN_PASSWORD=//p' /root/existdb-credentials.txt)
R=(--cacert /etc/ssl/existdb/existdb.crt --resolve "$H:443:127.0.0.1")
curl -s "${R[@]}" -K <(printf 'user = "admin:%s"\n' "$PW") -X PROPFIND -H 'Depth: 1' "https://$H/exist/webdav/db/library/" | grep -o '<d:href>[^<]*</d:href>'
curl -s -o /dev/null -w 'WebDAV, no login -> HTTP %{http_code}\n' "${R[@]}" -X PROPFIND -H 'Depth: 0' "https://$H/exist/webdav/db/"
curl -s -o /dev/null -w 'WebDAV, guest:guest -> HTTP %{http_code}\n' "${R[@]}" -K <(printf 'user = "guest:guest"\n') -X PROPFIND -H 'Depth: 0' "https://$H/exist/webdav/db/"
RPC='<?xml version="1.0"?><methodCall><methodName>hasCollection</methodName><params><param><value><string>/db/library</string></value></param></params></methodCall>'
printf 'XML-RPC, your password -> %s\n' "$(curl -s "${R[@]}" -K <(printf 'user = "admin:%s"\n' "$PW") -H 'Content-Type: text/xml' --data-binary "$RPC" "https://$H/exist/xmlrpc" | grep -o '<boolean>[01]</boolean>')"
printf 'XML-RPC, admin, empty -> %s\n' "$(curl -s "${R[@]}" -K <(printf 'user = "admin:"\n') -H 'Content-Type: text/xml' --data-binary "$RPC" "https://$H/exist/xmlrpc" | grep -o 'Wrong password[^<]*\|<boolean>[01]</boolean>' | head -1)"
<d:href>/exist/webdav/db/library/</d:href>
<d:href>/exist/webdav/db/library/books.xml</d:href>
WebDAV, no login -> HTTP 401
WebDAV, guest:guest -> HTTP 401
XML-RPC, your password -> <boolean>1</boolean>
XML-RPC, admin, empty -> Wrong password for user [admin]
To use eXist's Java admin client (a desktop application) from your workstation, install eXist-db 6.4.1 locally and point the client at this instance through an SSH tunnel, so the client talks to eXist on the instance's loopback address and nothing extra is opened:
ssh -i <your-key.pem> -N -L 8080:127.0.0.1:8080 ubuntu@<public-ip>
Then start the client (bin/client.sh on Linux or macOS, bin\client.bat on Windows) and connect to xmldb:exist://localhost:8080/exist/xmlrpc as admin with the password from the credentials file. Close the tunnel when you have finished. On the instance itself the client is installed as /opt/existdb/bin/client.sh; it takes the password only with -P, which would put it in the process list, so prefer the REST API or the tunnel for scripted work.
Step 8: Accounts and anonymous access
Only admin can sign in. guest and the accounts the bundled applications created are disabled, and eXide refuses anonymous use. SYSTEM is eXist's internal account and can never sign in, and nobody has no password and is used only for anonymous ownership:
H=$(sudo sed -n 's#^EXISTDB_URL=https://\([^/]*\)/.*#\1#p' /root/existdb-credentials.txt)
PW=$(sudo sed -n 's/^EXISTDB_ADMIN_PASSWORD=//p' /root/existdb-credentials.txt)
xq() { printf '<query xmlns="http://exist.sourceforge.net/NS/exist" wrap="no"><text><![CDATA[%s]]></text></query>' "$1" | curl -s --cacert /etc/ssl/existdb/existdb.crt --resolve "$H:443:127.0.0.1" -K <(printf 'user = "admin:%s"\n' "$PW") -H 'Content-Type: application/xml' --data-binary @- "https://$H/exist/rest/db"; echo; }
xq 'string-join(for $u in sm:list-users() return $u || (if (sm:is-account-enabled($u)) then " enabled" else " disabled"), codepoints-to-string(10))'
xq '"eXide guest use: " || doc("/db/apps/eXide/configuration.xml")//restrictions/@guest'
SYSTEM enabled
admin enabled
eXide disabled
guest disabled
nobody enabled
packageservice disabled
eXide guest use: no
Users you create in the User Manager (or with sm:create-account) can sign in everywhere admin can, with the permissions you give them. On the instance itself, eXist's own loopback port 8080 still answers anonymous REST reads as the built-in guest identity, as eXist ships it; that port is never reachable from the network.
Step 9: XQuery confinement and instance metadata
XQuery runs inside the eXist-db service as the unprivileged existdb user. It cannot read secrets on the system, the modules that reach outside the database are switched off, and requests to the instance metadata address are dropped:
H=$(sudo sed -n 's#^EXISTDB_URL=https://\([^/]*\)/.*#\1#p' /root/existdb-credentials.txt)
PW=$(sudo sed -n 's/^EXISTDB_ADMIN_PASSWORD=//p' /root/existdb-credentials.txt)
xq() { printf '<query xmlns="http://exist.sourceforge.net/NS/exist" wrap="no"><text><![CDATA[%s]]></text></query>' "$1" | curl -s --cacert /etc/ssl/existdb/existdb.crt --resolve "$H:443:127.0.0.1" -K <(printf 'user = "admin:%s"\n' "$PW") -H 'Content-Type: application/xml' --data-binary @- "https://$H/exist/rest/db" | sed -E 's/.*<message>([^[]*).*/\1/'; echo; }
xq 'util:system-property("user.name")'
xq 'try { unparsed-text("file:///etc/shadow") } catch * { "refused: " || $err:code }'
xq 'try { unparsed-text("file:///root/existdb-credentials.txt") } catch * { "refused: " || $err:code }'
xq 'import module namespace file = "http://exist-db.org/xquery/file"; file:exists("/etc")'
xq 'declare namespace http = "http://expath.org/ns/http-client"; 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 }'
existdb
refused: err:FOUT1170
refused: err:FOUT1170
err:XQST0059 error found while loading module file: failed to load module: 'http://exist-db.org/xquery/file' from: 'http://exist-db.org/xquery/file', location: 'http://exist-db.org/xquery/file'. Source not found.
refused: Connect to 169.254.169.254:80 [/169.254.169.254] failed: Connect timed out
The service settings behind it:
systemctl show existdb -p User -p NoNewPrivileges -p ProtectSystem -p ProtectHome -p PrivateTmp -p IPAddressDeny
systemctl show nginx -p IPAddressDeny
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
User=existdb
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
The switched-off modules are commented out in /opt/existdb/etc/conf.xml (search for cloudimg: disabled). If you need one, for example file to synchronise collections to disk, re-enable it there and run sudo systemctl restart existdb; it will still be confined to the data volume.
Step 10: Storage, memory and logs
eXist-db keeps its database files, journal and indexes under /var/lib/existdb/data, its logs under /var/lib/existdb/logs and its temporary files under /var/lib/existdb/tmp, all on the dedicated EBS volume. The JVM runs with a 2 GB heap:
df -h /var/lib/existdb | tail -1 | awk '{print $2" total, "$4" free on "$6}'
sudo ls /var/lib/existdb
sudo grep -oE -- '-Xmx[0-9a-z]+' /etc/existdb/existdb.env
sudo ls /var/lib/existdb/logs | grep -E '^(exist|xmldb|restxq)\.log$|request\.log$' | sed -E 's/^[0-9_]+\./<date>./'
20G total, 19G free on /var/lib/existdb
data
logs
lost+found
tmp
-Xmx2g
<date>.request.log
exist.log
restxq.log
xmldb.log
- Heap. On a larger instance raise
-Xmx2gin/etc/existdb/existdb.env(about half of the instance's memory is a good start), thensudo systemctl restart existdb. - 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/existdb)". - Backups. Run a backup from the Dashboard (Backup), or schedule eXist's consistency check and backup job in
/opt/existdb/etc/conf.xml; backups are written to the data volume. For a full copy, stop eXist-db (sudo systemctl stop existdb) and take an EBS snapshot of the data volume. Restore a snapshot as a whole volume: first boot never runs again on an instance that has completed it, and eXist-db refuses to start on an empty data directory (it would otherwise create a database with an empty admin password). - Logs.
exist.logis the main eXist log,<date>.request.logthe web server's request log;journalctl -u existdbshows the service output andjournalctl -u exist-db-firstbootwhat first boot did.
Step 11: Applications and Monex
The Dashboard's Package Manager installs further eXist applications and libraries from the public eXist package repository (the instance needs outbound HTTPS for that). New applications follow the permissions their packages set, so review what an application exposes to anonymous visitors before you install it.
Monex, eXist's monitoring application, is not pre-installed: its live view reads eXist's JMX status servlet, which this image switches off because that servlet trusts every request arriving through the local proxy. Monitor eXist-db with journalctl -u existdb, exist.log and your usual EC2 and CloudWatch tools instead.
Step 12: Use your own DNS name or certificate
The served host name is fixed by configuration, never taken from a request. To serve eXist-db 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 exist-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/existdb/existdb.crt (full chain) and /etc/ssl/existdb/existdb.key (mode 0600, owner root), then run sudo nginx -t && sudo systemctl reload nginx.
Step 13: 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 Dashboard, open User Manager, then admin, and set a new password, then update
/root/existdb-credentials.txt. adminand every member of thedbagroup can read files theexistdbuser can read, query other web services and install packages through XQuery. Use separate users withoutdbamembership for applications.- nginx logs the real client address of every request;
X-Forwarded-Forheaders sent by clients are ignored. The AMI requires IMDSv2. Restrict port 443 to the networks that use eXist-db. - Bundled libraries. eXist-db 6.4.1 is the newest eXist release (7.0 is still in beta) and ships libraries that a scanner reports with known advisories. None is reachable from the network in this configuration: Apache MINA, the JLine telnet server and the Spring Framework 2.5.6 jars are not used by any eXist component that is switched on; HSQLDB, Apache Tika, Apache FOP and Batik are reached only through the
sql,contentextractionandxslfomodules, which are switched off; Jetty 9.4.58 (request smuggling with chunk extensions, CVE-2026-2332, and a DIGEST authentication issue, CVE-2026-10050) is reached only through nginx, which re-frames every request, and eXist uses no DIGEST authentication; the Bouncy Castle advisories concern algorithms and certificate checks eXist does not use; commons-beanutils and JDOM are used by the WebDAV server only after a user has signed in. cloudimg updates the image when eXist ships a release with newer libraries.
When you have finished with the example collection from Step 5, remove it:
H=$(sudo sed -n 's#^EXISTDB_URL=https://\([^/]*\)/.*#\1#p' /root/existdb-credentials.txt)
PW=$(sudo sed -n 's/^EXISTDB_ADMIN_PASSWORD=//p' /root/existdb-credentials.txt)
curl -s -o /dev/null -w 'remove /db/library -> HTTP %{http_code}\n' --cacert /etc/ssl/existdb/existdb.crt --resolve "$H:443:127.0.0.1" -K <(printf 'user = "admin:%s"\n' "$PW") -X DELETE "https://$H/exist/rest/db/library"
remove /db/library -> HTTP 200
Support and licensing
cloudimg supports this image by email at support@cloudimg.co.uk. Include the instance ID, the version (eXist-db 6.4.1 on Ubuntu 24.04) and what you were doing.
eXist-db is open source software under the GNU Lesser General Public License, version 2.1; the licence is on the instance at /opt/existdb/LICENSE. There is no per user or per CPU licence fee. The AMI price covers cloudimg's hardening, first boot setup and support.
eXist-db is a trademark of the eXist-db Project. 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.