Deployment guide

Poweradmin 4.3.6

Overview

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. You do not edit zone files or write SQL.

The image runs three pieces together on one instance. PowerDNS Authoritative Server 5.1.5 answers DNS on port 53. Poweradmin 4.3.6 is the control panel on port 443. 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.

This guide covers launching and using the Poweradmin 4.3.6 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud Hardened Pi-hole, AdGuard Home & DNS Server AMIs listing on AWS Marketplace. You choose it as the software version when you subscribe and launch.

What you get:

  • Poweradmin 4.3.6 with the October 2026 security fixes. Version 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_gates and 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, because the upstream installer is deleted. First boot creates exactly one administrator with a password unique to your instance. It then proves that this password signs in and that admin/admin, admin/password and 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, with no TCP port at all. PowerDNS and Poweradmin sign in to it as their own system users (pdns and www-data) through MariaDB's unix_socket authentication. 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. 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, and it refuses names it does not host. It 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:8081 only, 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.
  • No firmware-update timer and no root password. fwupd-refresh is masked, because EC2 has no firmware that LVFS can update, so the image makes no outbound LVFS call. The root account is locked with no password hash.

What is on the image: Poweradmin 4.3.6 (the upstream release archive, checksum verified), PowerDNS Authoritative Server 5.1.5 with the MySQL backend (from repo.powerdns.com, held at 5.1.5), 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.
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, choose your Region, then choose Launch through EC2.

Choose m5.large, your key pair and a security group as described above.

To launch with the AWS CLI instead, 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, run this command and 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>
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>
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. All five units are then 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.5 VERSION = '4.3.6'

nginx serves ports 80 and 443, and PowerDNS answers DNS on port 53. The PowerDNS 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
4

Get your URL and administrator password

sudo cat /root/poweradmin-credentials.txt

The file holds the address the panel is served as, which is your instance's public DNS name by default. It also holds 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>

Only root and the service that needs each file can read the credentials file, the panel's settings (which hold the session key and the API key) and the TLS key:

sudo stat -c '%a %U:%G %n' /root/poweradmin-credentials.txt /var/www/poweradmin/config/settings.php /etc/powerdns/pdns.d/sac-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/sac-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. 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'
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
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.

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.

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 ::/0 for 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>
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/sac-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.

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

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
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'
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.5 (held) pdns.service, /etc/powerdns/pdns.d/sac-pdns.conf, API key in sac-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-sac-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.5 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 admin password after your first sign in and give everyone a named account with a permission template. Multi factor authentication is available: switch it on in the mfa section of /var/www/poweradmin/config/settings.php (see config/settings.defaults.php for the options).
  • Keep /root/poweradmin-credentials.txt and /var/www/poweradmin/config/settings.php as 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 SecureAnyCloud's hardening, first boot setup and support. Poweradmin, PowerDNS and MariaDB are trademarks of their respective owners; SecureAnyCloud is not affiliated with or endorsed by the Poweradmin project, PowerDNS or MariaDB. Screenshots show demonstration data.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24 hour response SLA

Deploy on AWS

Launch Poweradmin 4.3.6 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