Zonemaster 2026.1.2 on AWS User Guide
Overview
This guide covers launching and using Zonemaster 2026.1.2 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened Pi-hole, AdGuard Home & DNS Server AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other DNS and time servers in the listing are described on the listing's guide hub.
Zonemaster is an open source DNS delegation and DNSSEC quality checker, maintained jointly by The Swedish Internet Foundation (IIS) and AFNIC. Give it a domain and it investigates the zone from the root of the DNS tree down to the domain's own name servers, runs the Zonemaster test suite (Address, Basic, Connectivity, Consistency, Delegation, DNSSEC, Nameserver, Syntax, System and Zone) and returns a graded report of Info, Notice, Warning, Error and Critical findings. You drive it from a web GUI or from its JSON-RPC API. Zonemaster runs no DNS server of its own: it only sends DNS queries to the name servers of the domains you test.
The cloudimg image adds:
- A login before anything answers. Zonemaster has no login of its own and will test any domain it is given. On this image nginx requires HTTP Basic authentication for the web GUI and the JSON-RPC API, with a random password created for user
adminon your instance's first boot and stored only as a bcrypt hash, so your instance is never an open DNS-probing service. Nothing but SSH answers until first boot has created it. - HTTPS from the first boot. nginx serves Zonemaster on port 443 with a certificate generated on your instance's first boot (never baked into the image). Port 80 redirects to HTTPS.
- Zonemaster 2026.1.2, pinned and refreshed. The official
zonemaster/all-in-onecontainer for release 2026.1.2 (Engine v9.0.1, Backend v12.1.1, CLI v8.0.3, LDNS v5.1.0, GUI v5.1.0) is pinned by its image digest and shipped in the AMI, so it is never pulled at boot. Its Alpine Linux packages (Apache httpd 2.4.69, expat, zlib, OpenSSL 3.5.9) and the Perl modules with published security fixes (DBI, Net::DNS, Mojolicious, URI, HTTP::Date, Storable, Socket, HTTP::Tiny, HTML::Parser, podlators, Archive::Tar) are brought up to their fixed releases, and an unused module with no stable fix (Net::IDN::Encode) is removed. Zonemaster's own code is unchanged. - The container on loopback, with reduced privileges. It is published on
127.0.0.1:8080only, keeps just the Linux capabilities its supervisor and web server need, cannot gain new privileges, and its JSON-RPC backend and test agent run as the unprivilegedzonemasteraccount. The backend port (5000) never leaves the container. - Fixed host and real client addresses. The container is always handed your configured host name, its redirects use it over HTTPS, requests for a host name the instance does not serve are dropped, cross-origin write requests from other web sites are refused, and nginx logs the real client address.
- Metadata service blocked. The container, Docker and nginx cannot reach the instance metadata service, and the AMI requires IMDSv2.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on for Ubuntu and for Docker's repository.
What is on the image: Zonemaster 2026.1.2 (container cloudimg/zonemaster-all-in-one:2026.1.2, built from zonemaster/all-in-one:v5.1.0-2), Docker Engine, 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 with a route to the internet (Zonemaster needs outbound DNS, UDP and TCP port 53, to test domains).
- A security group that allows TCP 22 (SSH) from your management network and TCP 443 (the web GUI and API) and TCP 80 (redirects to HTTPS) from the networks your administrators connect from. Never open port 8080 or 5000.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Zonemaster 2026.1.2 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 container uses about 300 MiB of memory, so several domain tests run comfortably side by side.
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=zonemaster}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Zonemaster 2026.1.2 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot takes under a minute: it creates the certificate and the password, starts the Zonemaster container against a fresh results database and only then lets nginx start. Then every unit is active and the container is up, published on loopback only:
systemctl is-active docker zonemaster nginx zonemaster-firstboot
sudo docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
sudo docker image ls --format '{{.Repository}}:{{.Tag}}'
active
active
active
active
NAMES STATUS PORTS
zonemaster Up 5 minutes 127.0.0.1:8080->80/tcp
cloudimg/zonemaster-all-in-one:2026.1.2
Step 4: Check what the network can reach
sudo /usr/local/sbin/zonemaster-port-check.sh
off-box TCP exactly [22 80 443]; Zonemaster on loopback 8080; backend :5000 not on the host
ZONEMASTER_PORTS_OK
That is the complete network surface of a new instance: SSH, the HTTPS front door and the HTTP redirect.
Step 5: Read your password
First boot writes this instance's web address, the user name and its password to a root only file:
sudo cat /root/zonemaster-credentials.txt
# Zonemaster 2026.1.2 on Ubuntu 24.04 (cloudimg)
# Generated on this instance's first boot. These credentials are unique to THIS instance. Keep this file secret.
# Zonemaster has no login of its own: nginx requires this HTTP Basic credential for the web GUI and the JSON-RPC API.
ZONEMASTER_URL=https://<public-dns-name>/
ZONEMASTER_API=https://<public-dns-name>/api/
ZONEMASTER_USER=admin
ZONEMASTER_PASSWORD=<ZONEMASTER_PASSWORD>
...
None of these values exists in the image: the password and the certificate are created on your instance's first boot. The host name is your instance's public DNS name (or its IP address when it has none); Step 10 shows how to use your own.
Without the password the GUI and the API are both refused, and port 80 only redirects to HTTPS on your configured host name:
curl -sk -o /dev/null -w 'no password: %{http_code}\n' https://127.0.0.1/en/
curl -sk -o /dev/null -w 'API without password: %{http_code}\n' -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"version_info"}' https://127.0.0.1/api/
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' http://127.0.0.1/en/
no password: 401
API without password: 401
port 80: 301 -> https://<public-dns-name>/en/
Step 6: Sign in to the web GUI
Open https://<public-dns-name>/ in your browser. The certificate is self-signed until you install your own (Step 10), so your browser asks you to accept it once. It then prompts for a user name and password: sign in as admin with the password from Step 5. You land on the Zonemaster domain checkup page.

Step 7: Test a domain and read the graded report
Type a domain, for example zonemaster.net, and select Run test. Zonemaster walks the delegation from the root to the domain's own name servers and, usually within a minute, shows the graded report. The Filter severity levels row counts the findings as Info, Notice, Warning, Error and Critical, so you can see the health of the zone at a glance; Show options lets you test IPv4 or IPv6 only, or test a domain that is not yet delegated by giving its name servers and DS records yourself.

Below the summary the findings are grouped by test module. Each module carries badges with its per severity counts and expands to show every test case with its message; Expand all modules opens them all and the search box finds a specific message. History lists earlier tests of the same domain, and Export downloads the report.

Internationalised domain names work too: enter them in their native script (for example räksmörgås.se) and Zonemaster tests the punycode form.
Step 8: Automate with the JSON-RPC API
The backend that powers the GUI is a JSON-RPC API at https://<public-dns-name>/api/, behind the same login. The commands on this page read the password from your credentials file and hand it to curl as a configuration file on its standard input (-K -), so it never appears on a command line or in your shell history. Ask for the component versions:
zm_auth() { sudo sed -n 's/^ZONEMASTER_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/zonemaster-credentials.txt; }
zm_auth | curl -sk -K - -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"version_info"}' https://127.0.0.1/api/ | jq -c .result
{"zonemaster_ldns":"5.1.0","zonemaster_backend":"12.1.1","zonemaster_engine":"v9.0.1"}
Start a test, wait for test_progress to reach 100, then fetch the graded results and count them by severity:
zm_auth() { sudo sed -n 's/^ZONEMASTER_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/zonemaster-credentials.txt; }
zm_rpc() { zm_auth | curl -sk -K - -H 'Content-Type: application/json' -d "$1" https://127.0.0.1/api/; }
ID=$(zm_rpc '{"jsonrpc":"2.0","id":1,"method":"start_domain_test","params":{"domain":"iis.se","ipv4":true,"ipv6":false}}' | jq -r .result)
echo "test id: $ID"
until [ "$(zm_rpc "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"test_progress\",\"params\":{\"test_id\":\"$ID\"}}" | jq -r .result)" = 100 ]; do sleep 5; done
zm_rpc "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"get_test_results\",\"params\":{\"id\":\"$ID\",\"language\":\"en\"}}" \
| jq -r '.result.results | group_by(.level) | map("\(.[0].level): \(length)") | .[]'
test id: fe364f3c94d0da29
INFO: 78
NOTICE: 1
Each result carries its module, testcase, level and a human readable message. The same test is visible in the GUI at https://<public-dns-name>/en/result/<test id>. From your own workstation, call the API as curl -k -u admin https://<public-dns-name>/api/ ..., which prompts for the password. The full method list is in the Zonemaster Backend API documentation at doc.zonemaster.net.
Step 9: Check the login end to end
The image carries a self-test that proves the whole front door: no password and the usual weak passwords are refused for the GUI and the API, your instance's password works, a real domain test returns a graded report, and the container's own ports are refused on the network address:
sudo /usr/local/sbin/zonemaster-roundtrip.sh
no credential and weak credentials refused (401 from the nginx Basic gate) for the GUI and the API
per-instance credential -> GUI 200 and JSON-RPC version_info (Engine v9.0.1, Backend 12.1.1, LDNS 5.1.0)
zonemaster.net: graded report with 84 results across 10 test modules; :5000/:8080 refused on <private-ip>
ZONEMASTER_LOGIN_OK
Step 10: Use your own DNS name, certificate and password
Create a DNS record for your instance (for example zonemaster.example.com), then serve Zonemaster under that name. The helper creates a new certificate for it and pins nginx, the cross-origin guard and the host the container sees to it:
sudo zonemaster-set-hostname zonemaster.example.com
The name is remembered in /etc/cloudimg/zonemaster-hostname. To use a certificate from your own certificate authority, replace /etc/ssl/zonemaster/zonemaster.crt and /etc/ssl/zonemaster/zonemaster.key (keep the key 0600 root) and run sudo systemctl reload nginx.
sudo zonemaster-set-password creates a new random password and updates /root/zonemaster-credentials.txt; sudo zonemaster-set-password --stdin reads one you choose (12 or more characters) from standard input. nginx picks it up on the next request.
sudo zonemaster-set-password
Step 11: Metadata access is blocked
Zonemaster sends queries to whatever name servers a test points it at. The image drops any container traffic to the instance metadata service in Docker's DOCKER-USER firewall chain, and Docker, the Zonemaster unit and nginx are denied it by systemd as well:
sudo iptables -S DOCKER-USER
sudo docker exec zonemaster perl -MHTTP::Tiny -e 'print "metadata from inside the container: ", HTTP::Tiny->new(timeout => 5)->get("http://169.254.169.254/latest/meta-data/")->{status}, "\n"'
-N DOCKER-USER
-A DOCKER-USER -d 168.63.129.16/32 -j DROP
-A DOCKER-USER -d 169.254.169.254/32 -j DROP
metadata from inside the container: 599
599 means the connection never completed. The instance itself still uses IMDSv2 normally (first boot reads its host name from it).
Step 12: What cloudimg changed in the container, and updates
The image's build record lists the upstream container digest, the image that runs and every package and module brought up to its security release:
sed -n '1,5p' /usr/share/doc/cloudimg/zonemaster/BUILD-PROVENANCE.txt
Zonemaster 2026.1.2 on Ubuntu 24.04 - cloudimg build provenance
=====================================================================
Upstream product release : Zonemaster v2026.1.2 (Engine v9.0.1, CLI v8.0.3, Backend v12.1.1, LDNS v5.1.0, GUI v5.1.0)
Upstream container : zonemaster/all-in-one@sha256:ba35602d29298c1bbd9c121ea8e6f3d1fe9f73d11d1440bae57df86e310e0ada (zonemaster/all-in-one:v5.1.0-2, linux/amd64, 2026-10-01)
Runtime image : cloudimg/zonemaster-all-in-one:2026.1.2 id sha256:<image-id> (one layer)
- Ubuntu and Docker receive security updates automatically through
unattended-upgrades. - Zonemaster is the pinned 2026.1.2 container. cloudimg publishes a new version of this listing when a new Zonemaster release, or a security update to its container, is needed. Test results live in the Docker volume
zonemaster_db; launch the new version when you want to move, and keep the old instance until you no longer need its history.
Security recommendations
- Open ports 80 and 443 only to the networks your administrators use, and port 22 only to your management network. Anyone who signs in can make the instance send DNS queries to any domain or name server.
- Use your own DNS name and a certificate your browsers trust (Step 10), and change the password if it has been shared.
- The HTTP Basic password is a single shared credential for the GUI and the API. Give API automation its own instance, or rotate the password when people leave.
Support and licensing
cloudimg provides 24/7 support for this image at support@cloudimg.co.uk.
Zonemaster is licensed under the BSD 2-Clause licence (documentation CC BY 4.0); the verbatim licence and a notice are in /usr/share/doc/cloudimg/third-party-licences/, and the build record in /usr/share/doc/cloudimg/zonemaster/. Zonemaster is a project of The Swedish Internet Foundation and AFNIC; this image is provided by cloudimg and is not affiliated with or endorsed by the Zonemaster project, The Swedish Internet Foundation or AFNIC. The Zonemaster name is used only to identify the software.