Poweradmin 4.3.6 on AWS User Guide
Overview
This guide covers launching and using the Poweradmin 4.3.6 on Ubuntu 24.04 AMI 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.
Poweradmin is an open source web control panel for the PowerDNS Authoritative Server, from the Poweradmin project at poweradmin.org. In a browser you create forward and reverse zones, add and edit records, apply zone templates, sign zones with DNSSEC and hand zones to other people with permission templates, instead of editing zone files or writing SQL. The image runs three pieces together on one instance: PowerDNS Authoritative Server 5.1.4 answers DNS on port 53, Poweradmin 4.3.6 is the control panel on port 443, and MariaDB holds the zones and the panel's own tables in one database. A record you add in the panel is answered by the name server straight away.
The cloudimg image adds:
- Poweradmin 4.3.6 with the October 2026 security fixes. 4.3.6 fixes a multi factor authentication bypass and an account takeover through password reset that affect 4.3.5, and its PHP dependencies have no known advisories. Its two new hardening switches (
strict_session_gatesand single use password reset links) are turned on. - HTTPS from the first boot. nginx serves the panel on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
- Nobody can claim your instance. The image ships no account and no setup wizard (the upstream installer is deleted). First boot creates exactly one administrator with a password unique to your instance, and proves that it signs in and that
admin/admin,admin/passwordand a blank password do not. - A database with no password and no port. The MariaDB database itself is created on first boot, so no account, password or password hash from the build can be in it. MariaDB listens on its local socket only (no TCP port at all), and PowerDNS and Poweradmin sign in to it as their own system users (
pdnsandwww-data) through MariaDB'sunix_socketauthentication. There is no database password to manage or leak. - Login brute force protection. Five failed sign ins from one address lock that address out of the account for 15 minutes, and the address is always the one the connection came from, never a header a client can forge.
- An authoritative server that cannot be abused. PowerDNS never recurses (it refuses names it does not host), refuses zone transfers to everyone until you name a secondary, accepts no dynamic updates and does not reveal its version. Its REST API listens on
127.0.0.1:8081only, with a key unique to your instance. - Host names from configuration, never from a request. The names the panel answers to come from your instance's EC2 metadata (or a name you set). Requests for any other host name get no answer.
- Loopback only, metadata blocked. The only open ports are 22, 80, 443 and 53. MariaDB, PowerDNS, PHP and nginx are blocked from the instance metadata service, and the AMI requires IMDSv2.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Poweradmin 4.3.6 (the upstream release archive, checksum verified), PowerDNS Authoritative Server 5.1.4 with the MySQL backend (from repo.powerdns.com, held at 5.1.4), MariaDB 10.11, nginx, PHP 8.3 FPM, 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), TCP 443 (the panel) and TCP 80 (redirects to HTTPS) from your own networks only. The panel is an administration tool.
- DNS, port 53 (UDP and TCP), is closed by default. Open it to the internet only if this instance will be the public name server for your domains. Otherwise open it only to your own VPC or VPN ranges and query the instance's private IP address.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Poweradmin 4.3.6 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.
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=poweradmin}]'
To open DNS to your VPC only (replace the CIDR with your own range):
aws ec2 authorize-security-group-ingress --group-id <your-security-group> --protocol udp --port 53 --cidr <vpc-cidr>
aws ec2 authorize-security-group-ingress --group-id <your-security-group> --protocol tcp --port 53 --cidr <vpc-cidr>
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Poweradmin 4.3.6 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot creates the database, loads the PowerDNS and Poweradmin schemas, generates the secrets and the certificate and starts the services; allow about a minute after the instance reaches Running. Then all five units are active:
systemctl is-active mariadb pdns php8.3-fpm nginx poweradmin-firstboot
active
active
active
active
active
The versions:
pdns_server --version 2>&1 | grep -oE 'PowerDNS Authoritative Server [0-9.]+' | head -1
sudo grep -oE "VERSION = '[^']+'" /var/www/poweradmin/lib/Version.php
PowerDNS Authoritative Server 5.1.4
VERSION = '4.3.6'
nginx serves ports 80 and 443, PowerDNS answers DNS on port 53, its REST API is on loopback only and MariaDB has no network port at all:
sudo ss -tulnH | awk '{print $1, $5}' | sort -u
tcp 0.0.0.0:22
tcp 0.0.0.0:443
tcp 0.0.0.0:53
tcp 0.0.0.0:80
tcp 127.0.0.1:8081
tcp [::]:22
tcp [::]:443
tcp [::]:53
tcp [::]:80
udp 0.0.0.0:53
udp 127.0.0.1:323
udp <private-ip>%ens5:68
udp [::1]:323
udp [::]:53
Step 4: Get your URL and administrator password
sudo cat /root/poweradmin-credentials.txt
The file holds the address the panel is served as (your instance's public DNS name by default), the administrator user name and password, and the PowerDNS REST API key, all generated on this instance:
POWERADMIN_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/
poweradmin.admin.username=admin
poweradmin.admin.password=<POWERADMIN_ADMIN_PASSWORD>
powerdns.api.url=http://127.0.0.1:8081/api/v1/servers/localhost
powerdns.api.key=<POWERDNS_API_KEY>
powerdns.private.ip=<private-ip>
powerdns.public.ip=<public-ip>
The credentials file, the panel's settings (which hold the session key and the API key) and the TLS key are readable by root and the service that needs them only:
sudo stat -c '%a %U:%G %n' /root/poweradmin-credentials.txt /var/www/poweradmin/config/settings.php /etc/powerdns/pdns.d/cloudimg-secrets.conf /etc/ssl/poweradmin/poweradmin.key
600 root:root /root/poweradmin-credentials.txt
640 root:www-data /var/www/poweradmin/config/settings.php
640 root:pdns /etc/powerdns/pdns.d/cloudimg-secrets.conf
600 root:root /etc/ssl/poweradmin/poweradmin.key
No database account on the instance has a password. PowerDNS and Poweradmin sign in over the local socket as their own system users, and MariaDB refuses anyone else who claims those names:
sudo mariadb -t -e "SELECT User, Host, IF(authentication_string IN ('','invalid'),'none','SET') AS password FROM mysql.user ORDER BY User"
sudo -u www-data mariadb powerdns -N -B -e "SELECT COUNT(*) FROM domains"
sudo mariadb -u www-data powerdns -e "SELECT 1" 2>&1 | head -1 || true
+-------------+-----------+----------+
| User | Host | password |
+-------------+-----------+----------+
| mariadb.sys | localhost | none |
| mysql | localhost | none |
| pdns | localhost | none |
| root | localhost | none |
| www-data | localhost | none |
+-------------+-----------+----------+
1
ERROR 1698 (28000): Access denied for user 'www-data'@'localhost'
Step 5: Sign in
Open POWERADMIN_URL in your browser. The certificate is self signed (generated on your instance), so accept the browser warning once, or set your own name and certificate (Step 10).

Sign in as admin with the password from Step 4. The dashboard counts your zones, records, users and groups, and the PowerDNS tile reports Online: the panel is talking to the name server through its loopback REST API with your instance's key.

Change the password straight away: open Account, then Password. Passwords are stored as bcrypt hashes. Afterwards the copy in /root/poweradmin-credentials.txt is out of date; remove that line or the whole file.
The same sign in can be proved from the command line, through the real login form. The generated password is accepted and admin is refused:
sudo sed -n 's/^poweradmin.admin.password=//p' /root/poweradmin-credentials.txt | tr -d '\n' | sudo /usr/local/sbin/poweradmin-login-check.sh admin && echo "admin + the generated password: ACCEPTED"
printf 'admin' | sudo /usr/local/sbin/poweradmin-login-check.sh admin || echo "admin/admin: REJECTED"
admin + the generated password: ACCEPTED
admin/admin: REJECTED
Step 6: The demonstration zone
The image provisions one demonstration zone, example.com, so the panel has real records to show and DNS answers from the first minute. Open Zones, then Forward Zones:

Open the zone to see and edit its records in place, filter them, and add more:

The demonstration address 203.0.113.10 is reserved for documentation. PowerDNS answers for the zone authoritatively (the aa flag), on loopback and on the instance's private address:
dig +norec +noall +answer @127.0.0.1 SOA example.com
dig +norec +noall +answer @127.0.0.1 A www.example.com
dig +norec @"$(ip -4 route get 1.1.1.1 | sed -n 's/.* src \([0-9.]*\).*/\1/p')" A www.example.com | grep -E 'flags:|^www'
example.com. 3600 IN SOA ns1.example.com. hostmaster.example.com. 1 10800 3600 604800 3600
www.example.com. 3600 IN A 203.0.113.10
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
www.example.com. 3600 IN A 203.0.113.10
Delete the demonstration zone from the forward zones list once your own zones are in place.
Step 7: Add your own zone and records
In Zones, choose Add master zone, type your domain and save. The zone is created with an SOA record and is answered by PowerDNS straight away.

Open the new zone and choose Add record. Enter the host name (www, or @ for the zone itself), the type and the content, and add more rows for several records at once:

Before you delegate a real domain, set the name servers and the hostmaster address new zones get in their SOA and NS records: the ns1, ns2 and hostmaster values in the dns section of /var/www/poweradmin/config/settings.php (edit it as root, then sudo systemctl reload php8.3-fpm).
To script a bulk load, PowerDNS's own pdnsutil writes to the same database. Run it as the pdns user, which is how it signs in to MariaDB:
sudo -u pdns pdnsutil zone create example.org ns1.example.org
sudo -u pdns pdnsutil rrset add example.org www.example.org A 3600 203.0.113.30
dig +norec +noall +answer @127.0.0.1 A www.example.org
Creating empty zone 'example.org'
Also adding one NS record
www.example.org. 3600 IN A 203.0.113.30
www.example.org. 3600 IN A 203.0.113.30
A zone created either way appears in the panel. Remove the example with sudo -u pdns pdnsutil zone delete example.org.
Step 8: Serve your domain on the internet
To make this instance the public name server for a domain:
- Open UDP 53 and TCP 53 in the security group to
0.0.0.0/0(and::/0for IPv6). Keep 22, 80 and 443 limited to your own networks. PowerDNS only ever answers for the zones it hosts, so it cannot be used as an open resolver. - Give the instance a fixed address: allocate an Elastic IP and associate it with the instance.
- In your zone, set the NS records to the name server names you will use (for example
ns1.<your-domain>) and add an A record for each of those names pointing at the Elastic IP. - At your registrar, set the domain's name servers to those names and add glue records with the Elastic IP. Registrars and most top level domains want at least two name servers: run a second instance as a secondary (Step 9) or use a secondary DNS service.
Check what the world sees once the delegation has propagated:
dig +trace NS <your-domain>
dig @<elastic-ip> A www.<your-domain>
Step 9: Zone transfers and secondary name servers
Zone transfers (AXFR) are refused to everyone, so nobody can download your zones:
dig +tcp @127.0.0.1 example.com AXFR | grep -E 'Transfer failed|SOA' || true
; Transfer failed.
To feed a secondary name server, allow only that server's address. Add it to /etc/powerdns/pdns.d/cloudimg-pdns.conf and restart PowerDNS:
allow-axfr-ips=<secondary-ip>
also-notify=<secondary-ip>
sudo systemctl restart pdns
On the secondary, add the zone as a slave zone with this instance's address as its master (on a second instance of this image: Zones, Add slave zone). Leave TCP 53 open between the two servers.
Step 10: Your own DNS name and certificate
Point a DNS record at the instance, then set the name the panel is served as. This mints a new self signed certificate for the name, updates nginx, the panel's base URL and the URL in /root/poweradmin-credentials.txt, and keeps the name across reboots:
sudo poweradmin-set-hostname <your-domain>
To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running poweradmin-set-hostname again replaces it with a self signed one):
sudo install -m 0644 <your-domain>.crt /etc/ssl/poweradmin/poweradmin.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/poweradmin/poweradmin.key
sudo nginx -t && sudo systemctl reload nginx
Step 11: DNSSEC
Poweradmin signs zones through the PowerDNS REST API: open a zone and use DNSSEC to add keys and read the DS records to give your registrar. From the command line (shown here for a zone named example.net):
sudo -u pdns pdnsutil zone secure example.net
sudo -u pdns pdnsutil zone show example.net | grep '^DS = .*SHA256'
dig +dnssec +norec +noall +answer @127.0.0.1 A www.example.net
Zone example.net secured
DS = example.net. IN DS 44517 13 2 05e89f30...0e77 ; ( SHA256 digest )
www.example.net. 3600 IN RRSIG A 13 3 3600 20261029000000 20261008000000 44517 example.net. nDX0...buw==
www.example.net. 3600 IN A 203.0.113.20
The signing keys are created on your instance and stored in its database, so back them up with the database (Step 13).
Step 12: Security checks
PowerDNS refuses names it does not host, the API refuses a wrong key, an unknown host name gets no answer, port 80 redirects to HTTPS and the installer and settings file cannot be fetched:
dig @127.0.0.1 www.amazon.com A | grep -o 'status: [A-Z]*'
curl -s -o /dev/null -w 'PowerDNS API with a wrong key: %{http_code}\n' -H 'X-API-Key: wrong' http://127.0.0.1:8081/api/v1/servers/localhost
H=$(sudo sed -nE 's#^POWERADMIN_URL=https://([^/]+)/.*#\1#p' /root/poweradmin-credentials.txt)
curl -s -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: evil.example' http://127.0.0.1/ || true
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'setup wizard: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/install/"
curl -sk -o /dev/null -w 'settings file: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/config/settings.php"
status: REFUSED
PowerDNS API with a wrong key: 401
unknown host name: 000
port 80: 301 -> https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/
setup wizard: 404
settings file: 404
The login lockout and the 4.3.6 hardening switches are on:
sudo grep -E "'(enable_lockout|lockout_attempts|lockout_duration|strict_session_gates|single_use_claim)'" /var/www/poweradmin/config/settings.php
'strict_session_gates' => true,
'single_use_claim' => true,
'enable_lockout' => true,
'lockout_attempts' => 5,
'lockout_duration' => 15,
Only the instance's own loopback address is exempt from the lockout, for local tools such as poweradmin-login-check.sh. nginx hands the panel the address of the actual connection and blanks any X-Forwarded-For, X-Real-IP or Client-IP header a client sends, so a lockout cannot be dodged by forging one.
MariaDB, PowerDNS, PHP and nginx cannot reach the instance metadata service:
systemctl show mariadb pdns php8.3-fpm nginx -p IPAddressDeny
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
The image ships the check it is built and tested with. It proves the API key, both database users, the demonstration zone, the panel login with your password, that the common default passwords are refused, that a wrong uid is refused by MariaDB, that MariaDB has no TCP port and that zone transfers are refused:
sudo bash /usr/local/sbin/poweradmin-cred-roundtrip.sh
OK panel login, DNS, PowerDNS API and socket-only database round trip; defaults, wrong-uid DB logins and AXFR refused
Step 13: Backups
The database holds your zones, DNSSEC keys, users and permissions. Dump it to a root only directory and copy it off the instance (or snapshot the EBS volume):
sudo install -d -o root -g root -m 0700 /var/backups/poweradmin
sudo sh -c 'umask 077; mariadb-dump --single-transaction powerdns | gzip > /var/backups/poweradmin/powerdns-$(date -u +%Y%m%d).sql.gz'
Step 14: Server components
| Component | Version | Path |
|---|---|---|
| Poweradmin | 4.3.6 | /var/www/poweradmin, settings in config/settings.php (root:www-data, 0640) |
| PowerDNS Authoritative Server | 5.1.4 (held) | pdns.service, /etc/powerdns/pdns.d/cloudimg-pdns.conf, API key in cloudimg-secrets.conf |
| MariaDB | 10.11 | mariadb.service, socket /run/mysqld/mysqld.sock, data in /var/lib/mysql (created on first boot) |
| PHP | 8.3 FPM | php8.3-fpm.service, /etc/php/8.3/fpm/conf.d/99-cloudimg-poweradmin.ini |
| nginx | 1.24 | /etc/nginx/sites-available/poweradmin |
| Certificate and key | /etc/ssl/poweradmin/ |
|
| First boot | poweradmin-firstboot.service, /usr/local/sbin/poweradmin-firstboot.sh |
|
| Host name helper | /usr/local/sbin/poweradmin-set-hostname |
|
| Credentials | /root/poweradmin-credentials.txt (0600) |
PowerDNS is held at 5.1.4 so an unattended upgrade can never change your name server under you. To take a later 5.1 release from the PowerDNS repository:
sudo apt-mark unhold pdns-server pdns-backend-mysql
sudo apt-get update && sudo apt-get install pdns-server pdns-backend-mysql
sudo apt-mark hold pdns-server pdns-backend-mysql
Logs: journalctl -u pdns, journalctl -u poweradmin-firstboot, /var/log/nginx/, and Poweradmin's own log of user and zone changes in its database.
Troubleshooting
The panel does not answer. First boot may still be running (allow a minute), or it failed (then nginx, PHP and PowerDNS stay stopped by design). Check it:
systemctl show -p ActiveState -p Result poweradmin-firstboot.service
Result=success
ActiveState=active
If Result is not success, read sudo journalctl -u poweradmin-firstboot --no-pager; first boot runs again on the next boot until it succeeds.
The browser gets no answer for a name you use. The panel answers only to its configured name, the instance's own EC2 addresses and names, and localhost. Set your name with sudo poweradmin-set-hostname <your-domain> (Step 10).
You are locked out after mistyping the password. Wait 15 minutes; the lockout applies per address. To clear it at once: sudo mariadb powerdns -e "DELETE FROM login_attempts".
pdnsutil says access denied. Run it as the pdns user: sudo -u pdns pdnsutil .... As root it would claim the pdns database account from the wrong system user, which MariaDB refuses.
Security recommendations
- Allow ports 22, 80 and 443 from your own networks only. Open port 53 to the internet only for zones you serve publicly.
- Change the
adminpassword after your first sign in and give everyone a named account with a permission template. Multi factor authentication is available: switch it on in themfasection of/var/www/poweradmin/config/settings.php(seeconfig/settings.defaults.phpfor the options). - Keep
/root/poweradmin-credentials.txtand/var/www/poweradmin/config/settings.phpas shipped. The settings file holds the session key and the PowerDNS API key. - Allow zone transfers only to your own secondaries (Step 9).
- Use your own DNS name and a certificate your users trust (Step 10).
- Keep the operating system patched; unattended security upgrades are on.
- Back up the database regularly (Step 13).
Support and licensing
Poweradmin is open source under the GNU General Public License v3 (the licence ships at /usr/share/doc/poweradmin/LICENSE), PowerDNS Authoritative Server under the GNU General Public License v2 and MariaDB under the GNU General Public License v2; there is no per user or per CPU licence fee. The AMI price covers cloudimg's hardening, first boot setup and support. Poweradmin, PowerDNS and MariaDB are trademarks of their respective owners; cloudimg is not affiliated with or endorsed by the Poweradmin project, PowerDNS or MariaDB. Screenshots show demonstration data.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA