PowerDNS Admin

Posted on 7 2026

PowerDNS is perfectly manageable from the command line. pdnsutil add-record, pdnsutil create-zone, pdnsutil list-zone — all of it works, and for scripted or infrequent changes it is entirely sufficient. The problem appears when you want to make a quick DNS change while doing something else, or when you want to see the full state of your zones at a glance without piping output through grep. That is what PowerDNS Admin is for.

PowerDNS Admin is a web application that talks to the PowerDNS REST API and gives you a browser interface for managing zones, records, users, and API keys. It does not replace pdnsutil for everything, but it covers the common cases well and removes the friction of small DNS changes considerably.

What it actually gives you

A zone list on the dashboard showing all your internal zones at a glance. Click into a zone and you see all its records in a table, editable in place. Adding a record is a form with dropdowns for type. Deleting a record is a button. DNSSEC status is visible per zone. You can create new zones, set SOA parameters, and manage DNSSEC keys without remembering the exact pdnsutil incantation for each operation.

It also has multi-user support with role-based access, which is useful if you ever want to give someone else access to make DNS changes without handing them root on February.

What it does not do: it does not replace the Recursor configuration, it does not manage forwarder rules, and it has no awareness of the split between the Authoritative Server and the Recursor. It is purely a frontend for the Authoritative Server’s API.

Why Docker for this one

PowerDNS Admin is a Flask application that historically required Python 3.11 and a specific set of compiled dependencies. On Ubuntu 24.04, which ships Python 3.12, the traditional pip-based install is broken and requires pulling in a third-party Python PPA and managing a virtual environment carefully. The Docker image handles all of that and stays isolated from February’s system Python entirely.

The image to use is powerdnsadmin/pda-legacy, the community-maintained fork that tracks the current Python and Gunicorn stack. The older ngoduykhanh/powerdns-admin image is no longer maintained and does not work on Python 3.13.

If Docker is not already on February, the Docker article in this series covers the installation. For now, assume it is available.

Creating a database for PowerDNS Admin

PowerDNS Admin stores its own data separately from PowerDNS’s zone data. It needs its own database for user accounts, API settings, and session state.

sudo mariadb
CREATE DATABASE pdnsadmin;
CREATE USER 'pdnsadmin'@'localhost' IDENTIFIED BY 'your-strong-password';
GRANT ALL PRIVILEGES ON pdnsadmin.* TO 'pdnsadmin'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Running the container

Create a directory for the PowerDNS Admin configuration:

sudo mkdir -p /etc/powerdns-admin

Create /etc/powerdns-admin/config.py:

import os

# Database
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://pdnsadmin:your-strong-password@172.17.0.1/pdnsadmin'

# Secret key — generate with: openssl rand -hex 32
SECRET_KEY = 'your-secret-key-here'

# PowerDNS API connection
PDNS_STATS_URL = 'http://127.0.0.1:8081/'
PDNS_API_KEY = 'your-pdns-api-key-here'
PDNS_VERSION = '4.8.3'

# Basic settings
SIGNUP_ENABLED = False

The database URI uses 172.17.0.1 rather than localhost or 127.0.0.1 because from inside the Docker container, the host machine is reached via the Docker bridge IP. The PowerDNS API URL uses 127.0.0.1 because it is accessed from the host’s perspective via a port mapping. The PDNS_API_KEY must match the api-key value in /etc/powerdns/pdns.conf exactly.

Set SIGNUP_ENABLED = False immediately. The first user you create through the interface is automatically an administrator. After that, new accounts should be created by an admin, not by anyone who can reach the URL.

Create a systemd service to run the container. Create /etc/systemd/system/powerdns-admin.service:

[Unit]
Description=PowerDNS Admin
After=network.target docker.service
Requires=docker.service

[Service]
Restart=always
ExecStartPre=-/usr/bin/docker stop powerdns-admin
ExecStartPre=-/usr/bin/docker rm powerdns-admin
ExecStart=/usr/bin/docker run --name powerdns-admin \
  --net=host \
  -v /etc/powerdns-admin/config.py:/opt/powerdns-admin/configs/production.py:ro \
  -e FLASK_CONF=../configs/production.py \
  powerdnsadmin/pda-legacy:latest
ExecStop=/usr/bin/docker stop powerdns-admin

[Install]
WantedBy=multi-user.target

--net=host gives the container access to the host’s network stack directly, which is the simplest way to let it reach both the MariaDB instance and the PowerDNS API on localhost without additional network configuration.

Enable and start it:

sudo systemctl daemon-reload
sudo systemctl enable --now powerdns-admin

On first start, the container will pull the image and run the database migrations automatically. Watch the logs to confirm it starts cleanly:

sudo journalctl -u powerdns-admin -f

Look for Gunicorn reporting that it is listening. If you see database connection errors, check the bridge IP and credentials in config.py. If you see API errors, confirm the PDNS_API_KEY matches what is in pdns.conf and that the PowerDNS API is listening on 127.0.0.1:8081.

First login

PowerDNS Admin listens on port 9191 by default. From a browser on your LAN or over WireGuard:

http://february.home.arpa:9191

The first time you load it, you will be prompted to create an administrator account. Do this immediately, then log in and go to Settings to confirm the API connection is working. A green status indicator means the Authoritative Server is reachable and the API key is correct.

Your zones should be visible on the dashboard immediately. If they are not, the API connection is not working.

UFW

Allow access to port 9191 from the LAN and VPN only:

sudo ufw allow from 192.168.1.0/24 to any port 9191
sudo ufw allow from 10.10.0.0/24 to any port 9191

Do not expose port 9191 to the internet. PowerDNS Admin does not have the hardening you would want for a public-facing web application, and there is no reason for it to be publicly accessible.

Exposing it properly

Running on port 9191 over plain HTTP is fine for LAN access over WireGuard where the tunnel is already encrypted. If you want it on a clean internal hostname with HTTPS, the right approach is a reverse proxy: Nginx or Caddy in front of the Gunicorn process, with a certificate from an internal CA or a wildcard cert. That is worth its own article and will be covered when the series gets to reverse proxies.

What changes now

Adding a DNS record used to mean SSH-ing into February and running a pdnsutil command. Now it means opening a browser tab. The underlying mechanism is identical: PowerDNS Admin calls the same REST API that pdnsutil uses under the hood. The difference is that the common operations, adding records, checking what exists, spot-checking DNSSEC status, no longer require remembering syntax or consulting a man page.

pdnsutil is still the right tool for scripted changes, bulk imports, and anything that needs to happen in a pipeline. PowerDNS Admin is the right tool for everything else.