Deployment guide

H2 Database Engine 2.5.252

Overview

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. 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.

This guide covers launching and using H2 Database Engine 2.5.252 on Ubuntu 24.04 from SecureAnyCloud on Amazon EC2. It is one version of SecureAnyCloud's distributed and embedded SQL databases 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.

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, because whoever reaches it can type a JDBC URL. On this image, the console is reachable only after an HTTPS password check that nginx performs before any H2 page is served, and the H2 process runs in a sandbox. Give the console password only to people you would give a shell on the instance.

What you get:

  • 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 verifies 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. Before it passes any request to H2, nginx asks for a Basic authentication password (user admin) that is also generated on first boot. 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 that you can grow or snapshot on its own.
  • A fully patched Ubuntu 24.04 LTS base at build time, with unattended security upgrades switched on (they also keep OpenJDK and nginx patched). SSH accepts keys only, and root login is off. The firmware metadata refresh timer (fwupd-refresh) is masked because EC2 instances have no firmware to update through it.
  • 24/7 SecureAnyCloud support

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 use the console only occasionally, leave 443 closed and reach it through an SSH tunnel (Step 7). Never open 8082 or 9092. They listen on loopback, and this guide reaches them through SSH.
1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Select H2 Database Engine 2.5.252 on Ubuntu 24.04 as the software version, choose your Region, and select Launch through EC2. Choose m5.large, your key pair, and a security group as described above. Keep the second (data) volume. You can make it larger than 10 GiB at launch.

To launch with the AWS CLI instead, run the following command and 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}]'
2

Connect to your instance

Connect over SSH as the user shown below.

Version SSH user

H2 Database Engine 2.5.252 on Ubuntu 24.04 ubuntu

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

First boot takes about ten seconds. After it finishes, 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
4

Get your passwords

The non-secret part of the credentials file holds 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>
5

Open the H2 Console

Open H2_CONSOLE_URL in your browser. The certificate is self-signed and 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 SecureAnyCloud 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 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:

6

Run SQL

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

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 the password 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 ;, and type quit to leave.

7

Connect your application through an SSH tunnel

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

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.

8

More databases and users

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

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 instead of sa.

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, and 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

nginx serves your instance's own certificate:

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 = sac 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 open only appdb with its sa password:

The image's own round-trip check verifies all of this end to end. It confirms that no H2 content is served without the console password, that a wrong password is refused, that jdbc:h2:mem: and INIT=RUNSCRIPT logins are refused (90149), that the sa and console admin passwords are accepted and wrong ones refused, and that only ports 22 and 443 are 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
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. The command also reloads nginx:

sudo h2-console-tls h2.example.com

The names are kept in /etc/sac/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/sac-h2-server-name.conf, then run sudo nginx -t && sudo systemctl reload nginx. Update H2_CONSOLE_URL in the credentials file to match.

11

Change the passwords

  • Console password (Basic authentication): sudo htpasswd -B /etc/nginx/sac-h2.htpasswd admin asks for the new password twice. Then run 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, where h2-shell reads it.
  • 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.

12

Backup, updates and maintenance

  • Back up a running database with SQL, for example BACKUP TO '/var/lib/h2/backup-appdb.zip'; (the h2 user writes the file 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 run 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). If you need more for the page cache, change the setting with sudo systemctl edit h2.
  • Logs: use 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).

SecureAnyCloud provides 24/7 technical support for this image by email and live chat: sales@secureanycloud.com. All product names are trademarks of their respective owners. SecureAnyCloud is not affiliated with or endorsed by the H2 Group.

Deploy on AWS

Launch H2 Database Engine 2.5.252 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

Deploy on AWS