Databases AWS

H2 Database Engine 2.5.252 on AWS User Guide

| Product: H2 Database Engine 2.5.252 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using H2 Database Engine 2.5.252 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.

H2 is an open source relational database written in Java. Applications use it through JDBC, either embedded in their own process or, as on this image, through H2's TCP server, and people use it through the H2 Console, a browser based SQL tool with a schema tree, a SQL editor and an editable result grid. This image runs the official, unmodified H2 2.5.252 release on OpenJDK 21, with one JVM serving both the TCP server and the console.

Read this first: the H2 Console is a code execution tool. Anyone who gets into the H2 Console can run Java code on the instance as the h2 user (H2 SQL such as CREATE ALIAS, linked tables and RUNSCRIPT FROM a URL make that possible, by design). H2's own login form is not a security boundary: whoever reaches it can type a JDBC URL. On this image the console is only reachable after an HTTPS password check that nginx performs before any H2 page is served, and the H2 process is locked into a sandbox. Give the console password only to people you would give a shell on the instance.

What the cloudimg image adds:

  • No database and no password in the image. First boot creates this instance's application database appdb, with an sa password generated on the instance (H2 sets a database's administrator password when the database is created, so the image ships no database at all). First boot proves that a blank, default or wrong sa password is refused. All passwords are written to the root only file /root/h2-database-credentials.txt.
  • The console only over HTTPS, behind a password unique to your instance. nginx serves the H2 Console on port 443 with a certificate generated on first boot, and asks for a Basic authentication password (user admin) generated on first boot before it passes any request to H2. Port 80 is not open at all. nginx serves only your instance's own names and drops requests for any other host name.
  • Both H2 servers on loopback, and no new databases through them. The console listens on 127.0.0.1:8082 and the TCP server on 127.0.0.1:9092, so neither is reachable from the network. Both run with -ifExists: a console or JDBC user can open appdb (with its sa password) but cannot create a database, including an in memory jdbc:h2:mem: database or a URL with an INIT=RUNSCRIPT setting (Step 9 shows the refusal). H2's PostgreSQL protocol server is not started.
  • A console admin password of its own. The console's Preferences, Tools and Shutdown pages need a third password, generated on first boot. It is stored only as a salted hash in H2's settings file, never on a command line.
  • Hardened service. H2 runs as an unprivileged h2 user with no capabilities and a read only view of the system: it can write only /var/lib/h2, and it cannot see /root, /home, /var/log or nginx's key and password files. H2, nginx and the first boot service are blocked from the instance metadata service (H2 SQL can fetch a URL you name), and the AMI requires IMDSv2. H2 and nginx refuse to start until first boot has finished.
  • Your data on its own disk. The database files (/var/lib/h2) are on a separate 10 GiB EBS volume you can grow or snapshot on its own.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on (they also keep OpenJDK and nginx patched). SSH accepts keys only, root login is off, and the firmware metadata refresh timer (fwupd-refresh) is masked because EC2 instances have no firmware to update through it.

What is on the image: H2 Database Engine 2.5.252 (/opt/h2/h2-2.5.252.jar, the upstream jar, checksum verified, running as h2.service), OpenJDK 21 and nginx from Ubuntu, a helper h2-shell that opens an SQL shell on appdb, the application database appdb (created on first boot, with a small CUSTOMERS sample table), 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 with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH) and TCP 443 (the H2 Console over HTTPS) from your own admin network only. Because console access means code execution, never open 443 to 0.0.0.0/0. If you only use the console occasionally, leave 443 closed and reach it through an SSH tunnel (Step 7). Never open 8082 or 9092: they listen on loopback and the guide reaches them through SSH.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick H2 Database Engine 2.5.252 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. Keep the second (data) volume; you can make it larger than 10 GiB at launch.

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=h2-database}]'

Step 2: Connect to your instance

Version SSH user
H2 Database Engine 2.5.252 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot takes about ten seconds. Then the three units are active:

systemctl is-active h2-database-firstboot h2 nginx
active
active
active

H2 (the java process) listens on loopback only; nginx serves port 443 and nothing listens on port 80:

sudo ss -tlnp | awk 'NR==1 || /java|nginx|sshd/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'
State Local Peer
LISTEN 127.0.0.1:9092 users:(("java"))
LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN 0.0.0.0:22 users:(("sshd"),("systemd"))
LISTEN 127.0.0.1:8082 users:(("java"))
LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:22 users:(("sshd"),("systemd"))

The versions on the image:

java -XX:-UsePerfData -version 2>&1 | head -1
sha256sum /opt/h2/h2-2.5.252.jar
openjdk version "21.0.12.1" 2026-08-18
90b11dc413da82070ef15fc943282c1408e6ffd34fa68998335fea8603b5ff20  /opt/h2/h2-2.5.252.jar

Step 4: Get your passwords

The non secret part of the credentials file, the console address and the database connection details:

sudo grep -E '^H2_(CONSOLE_URL|CONSOLE_USERNAME|JDBC_URL|DB_USER)=' /root/h2-database-credentials.txt
H2_CONSOLE_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/
H2_CONSOLE_USERNAME=admin
H2_JDBC_URL=jdbc:h2:tcp://127.0.0.1:9092/appdb
H2_DB_USER=sa

The whole file (sudo cat /root/h2-database-credentials.txt) also holds the three passwords generated on this instance, each 32 letters and digits:

H2_CONSOLE_PASSWORD=<the console's HTTPS password, user admin>
H2_CONSOLE_TLS_SHA256=<the SHA-256 fingerprint of this instance's certificate>
H2_DB_PASSWORD=<the sa password of appdb>
H2_WEB_ADMIN_PASSWORD=<the console's Preferences / Tools password>

Step 5: Open the H2 Console

Open H2_CONSOLE_URL in your browser. The certificate is self signed (generated on your instance), so check its fingerprint against H2_CONSOLE_TLS_SHA256 and accept it once, or install your own certificate (Step 10). Your browser then asks for a user name and password: enter admin and H2_CONSOLE_PASSWORD. Nothing of H2 is sent to your browser before that.

H2's login form opens with the saved setting cloudimg appdb: the driver org.h2.Driver, the JDBC URL jdbc:h2:tcp://127.0.0.1:9092/appdb and the user sa. Enter H2_DB_PASSWORD and choose Connect.

The H2 Console login form with the driver org.h2.Driver, the JDBC URL jdbc:h2:tcp://127.0.0.1:9092/appdb, the user sa and a masked password

The console shows the schema tree of appdb on the left (the CUSTOMERS table, INFORMATION_SCHEMA and the users) and the SQL editor on the right:

The H2 Console after connecting to appdb: the CUSTOMERS table, INFORMATION_SCHEMA and Users in the schema tree, the toolbar, an empty SQL statement box and the help page

Step 6: Run SQL

Type a statement in the SQL box and choose Run (or press Ctrl+Enter). The result grid appears below; Edit lets you change rows in place.

The H2 Console with SELECT * FROM CUSTOMERS ORDER BY ID run, showing three rows: Ada Lovelace, Alan Turing and Grace Hopper with their e-mail addresses and sign up dates

On the instance itself, h2-shell opens the same database as sa; it reads the password from the credentials file and hands it to Java in a private file, so it never appears on a command line:

sudo h2-shell -sql "SELECT ID, NAME, EMAIL FROM CUSTOMERS ORDER BY ID"
ID | NAME         | EMAIL
1  | Ada Lovelace | ada@example.com
2  | Alan Turing  | alan@example.com
3  | Grace Hopper | grace@example.com
(3 rows, 9 ms)

Run sudo h2-shell with no arguments for an interactive shell (end each statement with ;, type quit to leave).

Step 7: Connect your application through an SSH tunnel

Port 9092 (JDBC) and port 8082 (the console without nginx) listen on loopback only. From your own computer, forward a local port over SSH:

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

Then connect any JDBC client (with the H2 2.5.252 driver jar on its classpath) to jdbc:h2:tcp://127.0.0.1:9092/appdb as sa with H2_DB_PASSWORD. Use the same driver version as the server.

To use the console without opening port 443 at all, tunnel it instead and browse to https://localhost:8443/ (your browser asks for the same Basic authentication password):

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

An application that runs on the instance itself connects to jdbc:h2:tcp://127.0.0.1:9092/appdb directly. To let application servers in your VPC reach H2, put them behind your own TLS proxy or an SSH tunnel; the H2 TCP protocol on this image is not encrypted, so the image never exposes it to the network.

Step 8: More databases and users

The servers run with -ifExists, so new databases are created on the instance, not through the console or JDBC. Stop H2, create the database as the h2 user (the shell asks for the new database's sa password twice and creates the file), then start H2 again:

sudo systemctl stop h2
sudo -u h2 java -XX:-UsePerfData -cp /opt/h2/h2-2.5.252.jar org.h2.tools.Shell
URL       jdbc:h2:/var/lib/h2/data/reports
Driver    org.h2.Driver
User      sa
Password  <new password, typed twice>
sql> quit
sudo systemctl start h2

The new database is then jdbc:h2:tcp://127.0.0.1:9092/reports. Within a database, create users with SQL, for example CREATE USER app PASSWORD '<password>'; GRANT SELECT, INSERT, UPDATE, DELETE ON CUSTOMERS TO app;, and give applications those users rather than sa.

Step 9: Security checks

Without the Basic authentication password the console path returns 401 from nginx, and the response carries nothing of H2; port 80 is closed; a request for a host name this instance does not serve is dropped:

curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/nginx/ssl/h2-console.crt https://127.0.0.1/
curl -s --cacert /etc/nginx/ssl/h2-console.crt https://127.0.0.1/ | grep -o '<title>.*</title>'
curl -s -o /dev/null -w 'port 80: %{http_code}\n' -m 5 http://127.0.0.1/ || true
curl -sk -o /dev/null -w 'other host: %{http_code}\n' -H 'Host: attacker.example' https://127.0.0.1/ || true
401
<title>401 Authorization Required</title>
port 80: 000
other host: 000

The certificate nginx serves is your instance's own:

echo | openssl s_client -connect 127.0.0.1:443 -servername localhost 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-3-88-164-216.compute-1.amazonaws.com, O = cloudimg H2 Console
X509v3 Subject Alternative Name: 
    DNS:ec2-3-88-164-216.compute-1.amazonaws.com, IP Address:3.88.164.216, DNS:ip-172-31-95-138.ec2.internal, IP Address:172.31.95.138, DNS:localhost, IP Address:127.0.0.1

H2 runs on loopback, refuses to create databases, and is never given a password on its command line:

systemctl show -p ExecStart --value h2 | grep -o 'argv\[\]=[^;]*'
argv[]=/usr/bin/java -XX:-UsePerfData -XX:MaxRAMPercentage=50 -Djava.net.preferIPv4Stack=true -Dh2.bindAddress=127.0.0.1 -cp /opt/h2/h2-2.5.252.jar org.h2.tools.Server -tcp -tcpPort 9092 -web -webPort 8082 -baseDir /var/lib/h2/data -ifExists -properties /var/lib/h2 

Even with the console password, an in memory database is refused, so a console user can only open appdb with its sa password:

The H2 Console login form after trying jdbc:h2:mem:scratch: the error Database "mem:scratch" not found, either pre-create it or allow remote database creation (not recommended in secure environments) 90149

The image's own round trip checks all of this end to end: no H2 content without the console password, a wrong one refused, jdbc:h2:mem: and INIT=RUNSCRIPT logins refused (90149), the sa and console admin passwords accepted and wrong ones refused, and only ports 22 and 443 reachable from the network:

sudo /usr/local/sbin/h2-roundtrip.sh | tail -1
OK-h2-console-tls-basicauth-roundtrip

H2 runs as h2 in a sandbox: it cannot read nginx's files or write outside /var/lib/h2, and H2, nginx and first boot cannot reach the instance metadata service:

systemctl show -p User -p ProtectSystem -p ReadWritePaths -p NoNewPrivileges h2
systemctl show -p IPAddressDeny h2 nginx h2-database-firstboot
User=h2
ReadWritePaths=/var/lib/h2
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32

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

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

Step 10: Your own DNS name and certificate

nginx answers only for the names in your instance's certificate: its public and private DNS names and addresses at first boot, plus localhost. To add a DNS name of your own or an Elastic IP, issue a new certificate that includes it (this also reloads nginx):

sudo h2-console-tls h2.example.com

The names are kept in /etc/cloudimg/h2-console-names. To use a certificate from your own CA instead, install the certificate and key as /etc/nginx/ssl/h2-console.crt and /etc/nginx/ssl/h2-console.key (mode 0600, owner root), add your name to /etc/nginx/cloudimg-h2-server-name.conf, then run sudo nginx -t && sudo systemctl reload nginx. Update H2_CONSOLE_URL in the credentials file to match.

Step 11: Change the passwords

  • Console password (Basic authentication): sudo htpasswd -B /etc/nginx/cloudimg-h2.htpasswd admin asks for the new password twice; then sudo systemctl reload nginx and update H2_CONSOLE_PASSWORD in the credentials file.
  • Database sa password: connect as sa and run ALTER USER SA SET PASSWORD '<new-password>';, then update H2_DB_PASSWORD in the credentials file (h2-shell reads it from there).
  • Console admin password (Preferences, Tools, Shutdown): H2 keeps it only as a salted SHA-256 hash in /var/lib/h2/.h2.server.properties and has no page to change it, so write a new hash with H2 stopped. The password (12 characters or more) goes to Python on standard input, never on a command line:
sudo systemctl stop h2
read -rs NEWPW && printf '%s' "$NEWPW" | sudo python3 -I -c 'import hashlib,os,re,sys; pw=sys.stdin.buffer.read(); assert len(pw)>=12; s=os.urandom(32); h=(s+hashlib.sha256(pw+s).digest()).hex(); p="/var/lib/h2/.h2.server.properties"; t=open(p).read(); open(p,"w").write(re.sub(r"(?m)^webAdminPassword=.*$","webAdminPassword="+h,t))'; unset NEWPW
sudo systemctl start h2

Then update H2_WEB_ADMIN_PASSWORD in the credentials file.

Step 12: Backup, updates and maintenance

  • Back up a running database with SQL, for example BACKUP TO '/var/lib/h2/backup-appdb.zip'; (the file is written by the h2 user, under /var/lib/h2), or snapshot the data volume. SCRIPT TO '/var/lib/h2/appdb.sql'; writes a portable SQL dump.
  • Grow the data volume in the EC2 console, then sudo resize2fs <device> (find the device with findmnt /var/lib/h2).
  • Memory: the JVM may use up to half of the instance's memory (-XX:MaxRAMPercentage=50). Change it with sudo systemctl edit h2 if you need more for the page cache.
  • Logs: sudo journalctl -u h2 -n 50 for H2, /var/log/nginx/access.log for console requests (with the client address as nginx saw it), and H2's own trace file /var/lib/h2/data/appdb.trace.db.
  • Updates: Ubuntu security updates install automatically. H2 is pinned to the version this image was built with; upgrade it deliberately, after a backup, by replacing the jar and the path in h2.service.

Support and licensing

H2 is open source software available under the Mozilla Public License 2.0 or the Eclipse Public License 1.0, at your choice (/usr/share/doc/h2database/LICENSE.txt); the jar on this image is the unmodified upstream release (/usr/share/doc/h2database/BUILD-PROVENANCE.txt). cloudimg provides 24/7 technical support for this image by email and live chat: support@cloudimg.co.uk. All product names are trademarks of their respective owners; cloudimg is not affiliated with or endorsed by the H2 Group.