Deploying PowerDNS Authoritative DNS Server with Database Backend
Deploy PowerDNS with MySQL or PostgreSQL backend. Covers zone management, DNSSEC, the REST API, high availability, and DNS automation.
PowerDNS Architecture
1. Introduction to PowerDNS
PowerDNS is an open-source DNS server written in C++ that stores zone data in relational databases instead of flat files. It was first released in 1999 and is now maintained by Open-Xchange. Major users include hosting providers, registrars, and enterprises running millions of zones.
The key differentiator is the backend architecture. Instead of editing zone files by hand, you store records in MySQL, PostgreSQL, or another database. This makes PowerDNS a natural fit for web hosting panels, domain registrar platforms, and any system that needs to create or modify DNS records programmatically.
PowerDNS also ships with a built-in REST API, native DNSSEC support with online signing, and a statistics interface. It handles AXFR/IXFR zone transfers and can act as both a primary and secondary authoritative server.
This guide covers installation, configuration, database setup, DNSSEC, API usage, performance tuning, and operational best practices. All commands target PowerDNS Authoritative Server 4.x on Linux.
2. PowerDNS architecture overview
The PowerDNS ecosystem consists of three separate daemons:
- pdns_server — the authoritative server. Answers queries for zones it hosts. This is the focus of this guide.
- pdns_recursor — a recursive resolver. Resolves queries on behalf of clients by querying other authoritative servers. Shipped as a separate package.
- dnsdist — a DNS load balancer and traffic router. Sits in front of pdns_server or pdns_recursor to distribute queries, apply rate limits, and route traffic.
These are independent processes. You can run any combination. A typical authoritative-only deployment runs just pdns_server. A full-stack setup might run all three: dnsdist on port 53, forwarding authoritative queries to pdns_server and recursive queries to pdns_recursor.
The authoritative server uses a pluggable backend system. Each backend is a shared library that pdns_server loads at startup. The backend translates DNS lookups into database queries (or file reads, or LDAP queries, depending on the backend). Multiple backends can be loaded simultaneously.
Query flow: client sends a DNS query to pdns_server on port 53. The server parses the question, selects the appropriate backend based on the zone, queries the backend for matching records, assembles the DNS response, and sends it back. DNSSEC signing happens inline during response assembly.
3. Authoritative server vs recursor
These are fundamentally different roles in DNS. Confusing them is a common source of misconfiguration.
The authoritative server (pdns_server) holds the source-of-truth data for your zones. When someone queries for a record in example.com and your server is authoritative for example.com, it answers directly from its database. It does not chase referrals or query other servers.
The recursor (pdns_recursor) does not hold zone data. It receives queries from local clients, walks the DNS tree starting from the root servers, and returns the final answer. It caches results to speed up subsequent lookups.
In BIND, both roles are combined in a single daemon. PowerDNS deliberately separates them. This separation improves security (different attack surfaces), performance (each can be tuned independently), and clarity (no ambiguity about which data is authoritative vs cached).
Do not run both pdns_server and pdns_recursor on the same IP and port. If you need both on one machine, bind them to different addresses or use dnsdist to route traffic.
For hosting your own zones (the most common use case), you only need pdns_server. You do not need the recursor unless you also want to provide recursive resolution for your LAN clients.
4. Supported operating systems
PowerDNS runs on most Unix-like systems. The project provides official packages for:
- Debian 11 (Bullseye), 12 (Bookworm)
- Ubuntu 22.04 LTS (Jammy), 24.04 LTS (Noble)
- RHEL / AlmaLinux / Rocky Linux 8, 9
- CentOS Stream 8, 9
- Fedora (latest two releases)
- FreeBSD via ports/pkg
The PowerDNS project hosts its own repository at repo.powerdns.com with up-to-date packages. Distribution repositories also carry PowerDNS but often lag behind by several minor versions. Use the official repo for production deployments.
On RHEL-family systems, you also need the EPEL repository for some dependencies. macOS is not officially supported for production use but works for development via Homebrew.
5. Installing PowerDNS
Debian / Ubuntu:
apt update
apt install pdns-server pdns-tools
RHEL 9 / AlmaLinux 9 / Rocky 9:
dnf install epel-release
dnf install pdns pdns-tools
Using the official PowerDNS repository (recommended):
For Debian/Ubuntu, add the repo and key:
echo "deb [signed-by=/etc/apt/keyrings/powerdns.asc] https://repo.powerdns.com/debian bookworm-auth-49 main" \
> /etc/apt/sources.list.d/powerdns.list
curl -fsSL https://repo.powerdns.com/FD380FBB-pub.asc \
-o /etc/apt/keyrings/powerdns.asc
cat > /etc/apt/preferences.d/powerdns << 'EOF'
Package: pdns-*
Pin: origin repo.powerdns.com
Pin-Priority: 600
EOF
apt update
apt install pdns-server pdns-tools
For RHEL-family, create the repo file:
cat > /etc/yum.repos.d/powerdns-auth-49.repo << 'EOF'
[powerdns-auth-49]
name=PowerDNS Authoritative 4.9
baseurl=https://repo.powerdns.com/el/$releasever/$basearch/auth-49
gpgkey=https://repo.powerdns.com/FD380FBB-pub.asc
gpgcheck=1
enabled=1
EOF
dnf install pdns pdns-tools
After installation, the service is called pdns. Do not start it yet — it needs backend configuration first.
6. Installing database backend (MySQL/PostgreSQL)
PowerDNS needs at least one backend to store zone data. The two most common choices are MySQL/MariaDB and PostgreSQL.
Option A: MySQL / MariaDB
# Debian/Ubuntu
apt install pdns-backend-mysql mariadb-server
# RHEL/AlmaLinux
dnf install pdns-backend-mysql mariadb-server
systemctl enable --now mariadb
mysql_secure_installation
Create the database and user:
mysql -u root -p << 'EOF'
CREATE DATABASE pdns CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'pdns'@'127.0.0.1' IDENTIFIED BY 'CHANGE_THIS_PASSWORD';
GRANT ALL ON pdns.* TO 'pdns'@'127.0.0.1';
FLUSH PRIVILEGES;
EOF
Import the schema. The schema file is included with the backend package:
mysql -u pdns -p pdns < /usr/share/doc/pdns-backend-mysql/schema.mysql.sql
On some distributions, the path may be /usr/share/pdns-backend-mysql/schema/schema.mysql.sql. Check with dpkg -L pdns-backend-mysql | grep schema or rpm -ql pdns-backend-mysql | grep schema.
The schema creates these core tables:
-- domains: one row per zone
CREATE TABLE domains (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
master VARCHAR(128) DEFAULT NULL,
last_check INT DEFAULT NULL,
type VARCHAR(8) NOT NULL, -- MASTER, SLAVE, or NATIVE
notified_serial INT UNSIGNED DEFAULT NULL,
account VARCHAR(40) DEFAULT NULL,
options VARCHAR(65535) DEFAULT NULL,
catalog VARCHAR(255) DEFAULT NULL,
UNIQUE KEY name_index (name)
);
-- records: one row per DNS record
CREATE TABLE records (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
domain_id INT DEFAULT NULL,
name VARCHAR(255) DEFAULT NULL,
type VARCHAR(10) DEFAULT NULL, -- A, AAAA, MX, CNAME, etc.
content VARCHAR(65535) DEFAULT NULL,
ttl INT DEFAULT NULL,
prio INT DEFAULT NULL, -- used by MX, SRV
disabled TINYINT(1) DEFAULT 0,
ordername VARCHAR(255) BINARY DEFAULT NULL,
auth TINYINT(1) DEFAULT 1,
CONSTRAINT FOREIGN KEY (domain_id) REFERENCES domains(id) ON DELETE CASCADE
);
Option B: PostgreSQL
# Debian/Ubuntu
apt install pdns-backend-pgsql postgresql
# RHEL/AlmaLinux
dnf install pdns-backend-pgsql postgresql-server
postgresql-setup --initdb
systemctl enable --now postgresql
Create the database and user:
sudo -u postgres psql << 'EOF'
CREATE USER pdns WITH PASSWORD 'CHANGE_THIS_PASSWORD';
CREATE DATABASE pdns OWNER pdns;
EOF
Import the schema:
sudo -u postgres psql pdns < /usr/share/doc/pdns-backend-pgsql/schema.pgsql.sql
7. PowerDNS configuration files
The main configuration file location depends on your distribution:
- Debian/Ubuntu:
/etc/powerdns/pdns.conf - RHEL/AlmaLinux:
/etc/pdns/pdns.conf
The file uses a simple key=value format, one directive per line. Lines starting with # are comments. There are no sections or blocks.
Some backend-specific settings can live in separate files included via:
include-dir=/etc/powerdns/pdns.d
On Debian/Ubuntu, the backend config is typically placed in /etc/powerdns/pdns.d/ as a .conf file (e.g., gmysql.conf). On RHEL, everything usually goes in /etc/pdns/pdns.conf directly.
Key global settings to review:
# Network
local-address=0.0.0.0,::
local-port=53
# Backend (loaded via launch=)
launch=gmysql
# Security
setuid=pdns
setgid=pdns
# General
daemon=yes
guardian=yes
default-ttl=3600
default-soa-content=ns1.example.com hostmaster.example.com 0 10800 3600 604800 3600
The default-soa-content template is used when creating zones without an explicit SOA. The serial 0 tells PowerDNS to auto-calculate it.
After any config change, restart the service:
systemctl restart pdns
8. Connecting PowerDNS to database backend
MySQL/MariaDB (gmysql)
Add these lines to pdns.conf (or create /etc/powerdns/pdns.d/gmysql.conf):
launch=gmysql
gmysql-host=127.0.0.1
gmysql-port=3306
gmysql-dbname=pdns
gmysql-user=pdns
gmysql-password=CHANGE_THIS_PASSWORD
gmysql-dnssec=yes
Using 127.0.0.1 instead of localhost forces a TCP connection rather than a Unix socket. This avoids authentication issues on systems where the socket path differs.
PostgreSQL (gpgsql)
launch=gpgsql
gpgsql-host=127.0.0.1
gpgsql-port=5432
gpgsql-dbname=pdns
gpgsql-user=pdns
gpgsql-password=CHANGE_THIS_PASSWORD
gpgsql-dnssec=yes
SQLite 3 (gsqlite3)
For small deployments or testing:
launch=gsqlite3
gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
gsqlite3-dnssec=yes
Import the schema:
sqlite3 /var/lib/powerdns/pdns.sqlite3 < /usr/share/doc/pdns-backend-sqlite3/schema.sqlite3.sql
chown pdns:pdns /var/lib/powerdns/pdns.sqlite3
Test the connection by starting the service and checking the log:
systemctl restart pdns
journalctl -u pdns --no-pager -n 20
A successful startup shows gmysql Connection successful (or equivalent for your backend) and About to create 3 backend threads.
9. Creating DNS zones
The preferred way to create zones is with pdnsutil. This tool writes directly to the backend database.
pdnsutil create-zone example.com ns1.example.com
This creates a NATIVE zone with a SOA record pointing to ns1.example.com as the primary nameserver. The hostmaster defaults to hostmaster.example.com.
To create a MASTER zone (for zone transfers to secondaries):
pdnsutil create-zone example.com ns1.example.com
pdnsutil set-kind example.com master
To create a SLAVE zone (fetched from a remote master):
pdnsutil create-slave-zone example.com 10.0.0.1
Where 10.0.0.1 is the IP of the master server to AXFR from.
You can also create zones directly via SQL:
INSERT INTO domains (name, type) VALUES ('example.com', 'NATIVE');
-- Get the domain_id
SELECT id FROM domains WHERE name = 'example.com';
-- Assume id = 1
INSERT INTO records (domain_id, name, type, content, ttl)
VALUES
(1, 'example.com', 'SOA', 'ns1.example.com hostmaster.example.com 2024010101 10800 3600 604800 3600', 86400),
(1, 'example.com', 'NS', 'ns1.example.com', 86400),
(1, 'example.com', 'NS', 'ns2.example.com', 86400),
(1, 'ns1.example.com', 'A', '192.168.1.10', 3600),
(1, 'ns2.example.com', 'A', '192.168.1.11', 3600);
After creating a zone, verify it:
pdnsutil list-zone example.com
pdnsutil check-zone example.com
10. Managing records
Adding records with pdnsutil:
# A record
pdnsutil add-record example.com www A 3600 192.168.1.20
# AAAA record
pdnsutil add-record example.com www AAAA 3600 2001:db8::20
# MX record (priority is part of the content)
pdnsutil add-record example.com '' MX 3600 '10 mail.example.com'
# CNAME
pdnsutil add-record example.com cdn CNAME 3600 cdn.provider.com
# TXT record (SPF)
pdnsutil add-record example.com '' TXT 3600 '"v=spf1 mx ~all"'
# SRV record
pdnsutil add-record example.com '_sip._tcp' SRV 3600 '10 60 5060 sip.example.com'
Deleting records:
# Delete a specific record
pdnsutil delete-rrset example.com www A
# Delete all records of a name (all types)
pdnsutil delete-rrset example.com www ANY
Replacing records:
pdnsutil replace-rrset example.com www A 3600 192.168.1.21
This replaces all existing A records for www.example.com with a single new one.
Editing via SQL:
-- Update a record's content
UPDATE records SET content = '192.168.1.25' WHERE name = 'www.example.com' AND type = 'A';
-- Increase SOA serial (if not using auto-serial)
UPDATE records SET content = 'ns1.example.com hostmaster.example.com 2024010102 10800 3600 604800 3600'
WHERE name = 'example.com' AND type = 'SOA';
After modifying records, either increase the SOA serial or rely on PowerDNS auto-serial calculation. If you changed records via SQL, notify secondaries:
pdns_control notify example.com
11. DNSSEC configuration
PowerDNS supports online DNSSEC signing. Records are signed at query time, not pre-signed on disk. This simplifies key management and eliminates the need to re-sign after every record change.
Enable DNSSEC for the backend (already done if you set gmysql-dnssec=yes in section 8).
Secure a zone:
pdnsutil secure-zone example.com
This generates a KSK (Key Signing Key) and a ZSK (Zone Signing Key) using the default algorithm (ECDSA P-256 / algorithm 13). To use a specific algorithm:
pdnsutil secure-zone example.com
pdnsutil set-nsec3 example.com '1 0 1 ab' narrow
NSEC3 with narrow mode is recommended for zones with many delegations or where you want to prevent zone enumeration.
View zone DNSSEC status:
pdnsutil show-zone example.com
This outputs the DS records and DNSKEY records. Copy the DS record and submit it to your domain registrar.
Rectify the zone (required after enabling NSEC3 or after direct SQL modifications):
pdnsutil rectify-zone example.com
Key rollover:
# Generate a new key
pdnsutil add-zone-key example.com zsk active ecdsa256
# Deactivate the old key (get key ID from show-zone output)
pdnsutil deactivate-zone-key example.com 1
# After propagation, remove the old key
pdnsutil remove-zone-key example.com 1
Disable DNSSEC:
pdnsutil disable-dnssec example.com
Remove the DS record from the parent zone before disabling DNSSEC locally, or the zone will become unresolvable.
12. Using PowerDNS API
PowerDNS has a built-in REST API for managing zones and records over HTTP. Enable it in pdns.conf:
api=yes
api-key=YOUR_SECRET_API_KEY_HERE
webserver=yes
webserver-address=127.0.0.1
webserver-port=8081
webserver-allow-from=127.0.0.1,::1
Restart PowerDNS and test:
systemctl restart pdns
curl -s -H 'X-API-Key: YOUR_SECRET_API_KEY_HERE' \
http://127.0.0.1:8081/api/v1/servers/localhost | python3 -m json.tool
List all zones:
curl -s -H 'X-API-Key: YOUR_SECRET_API_KEY_HERE' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones
Create a zone:
curl -s -X POST \
-H 'X-API-Key: YOUR_SECRET_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"name": "example.com.",
"kind": "Native",
"nameservers": ["ns1.example.com.", "ns2.example.com."]
}' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones
Note the trailing dots on all domain names. The API uses fully-qualified domain names.
Add records (PATCH an RRset):
curl -s -X PATCH \
-H 'X-API-Key: YOUR_SECRET_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"rrsets": [{
"name": "www.example.com.",
"type": "A",
"ttl": 3600,
"changetype": "REPLACE",
"records": [
{"content": "192.168.1.20", "disabled": false}
]
}]
}' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.com.
Delete records:
curl -s -X PATCH \
-H 'X-API-Key: YOUR_SECRET_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"rrsets": [{
"name": "www.example.com.",
"type": "A",
"changetype": "DELETE"
}]
}' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.com.
Delete a zone:
curl -s -X DELETE \
-H 'X-API-Key: YOUR_SECRET_API_KEY_HERE' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.com.
Get server statistics:
curl -s -H 'X-API-Key: YOUR_SECRET_API_KEY_HERE' \
http://127.0.0.1:8081/api/v1/servers/localhost/statistics
13. Automating DNS management
The API and pdnsutil make automation straightforward. Here are common patterns.
Shell script to add a host:
#!/bin/bash
ZONE="example.com"
HOST="$1"
IP="$2"
API_KEY="YOUR_SECRET_API_KEY_HERE"
API_URL="http://127.0.0.1:8081/api/v1/servers/localhost/zones/${ZONE}."
curl -s -X PATCH \
-H "X-API-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"rrsets\": [{
\"name\": \"${HOST}.${ZONE}.\",
\"type\": \"A\",
\"ttl\": 3600,
\"changetype\": \"REPLACE\",
\"records\": [{\"content\": \"${IP}\", \"disabled\": false}]
}]
}" "${API_URL}"
echo "Added ${HOST}.${ZONE} -> ${IP}"
Usage: ./add-host.sh www 192.168.1.20
Bulk import from CSV:
#!/bin/bash
API_KEY="YOUR_SECRET_API_KEY_HERE"
ZONE="example.com"
while IFS=, read -r name type content ttl; do
curl -s -X PATCH \
-H "X-API-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"rrsets\": [{
\"name\": \"${name}.${ZONE}.\",
\"type\": \"${type}\",
\"ttl\": ${ttl},
\"changetype\": \"REPLACE\",
\"records\": [{\"content\": \"${content}\", \"disabled\": false}]
}]
}" \
"http://127.0.0.1:8081/api/v1/servers/localhost/zones/${ZONE}."
done < records.csv
pdnsutil in scripts:
# List all zones
pdnsutil list-all-zones
# Export zone in BIND format (useful for backups)
pdnsutil list-zone example.com
# Check all zones for errors
for zone in $(pdnsutil list-all-zones); do
pdnsutil check-zone "$zone"
done
For more complex automation, consider the Python powerdns library or the Go powerdns client, both available on PyPI and GitHub respectively.
14. Firewall configuration
DNS uses both UDP and TCP on port 53. TCP is required for zone transfers (AXFR), large responses, and DNS over TCP fallback. Open both protocols.
firewalld (RHEL/AlmaLinux):
firewall-cmd --permanent --add-service=dns
firewall-cmd --reload
firewall-cmd --list-services
iptables:
iptables -A INPUT -p udp --dport 53 -j ACCEPT
iptables -A INPUT -p tcp --dport 53 -j ACCEPT
iptables-save > /etc/iptables/rules.v4
nftables:
nft add rule inet filter input udp dport 53 accept
nft add rule inet filter input tcp dport 53 accept
Restrict the API webserver. The API should never be exposed to the internet. Bind it to localhost (webserver-address=127.0.0.1) and block port 8081 externally:
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="8081" protocol="tcp" reject'
firewall-cmd --reload
If you need remote API access, use an SSH tunnel or a reverse proxy with TLS and authentication rather than exposing the webserver directly.
Restrict zone transfers. In pdns.conf, limit AXFR to specific IPs:
allow-axfr-ips=192.168.1.11/32,10.0.0.0/24
disable-axfr=no
Set disable-axfr=yes if you do not use zone transfers at all.
15. Logging configuration
PowerDNS logs to syslog by default. Relevant pdns.conf settings:
# Log verbosity: 0 = errors only, 3 = normal, 5 = debug, 9 = trace
loglevel=4
# Log DNS queries (high I/O, disable in production)
log-dns-queries=no
# Log DNS details (query details in the log)
log-dns-details=no
# Syslog facility
logging-facility=0
View logs:
# systemd journal
journalctl -u pdns -f
# Traditional syslog (if configured)
tail -f /var/log/syslog | grep pdns
Enable query logging temporarily for debugging:
# Turn on without restart
pdns_control set loglevel 5
pdns_control set log-dns-queries yes
# Watch the queries
journalctl -u pdns -f
# Turn off when done
pdns_control set log-dns-queries no
pdns_control set loglevel 4
Structured logging to file: If you need logs outside syslog, use a logging-facility directive and rsyslog/syslog-ng to route the facility to a specific file. Or run PowerDNS in foreground mode during debugging:
pdns_server --daemon=no --guardian=no --loglevel=7 2>&1 | tee /tmp/pdns-debug.log
16. Performance tuning
PowerDNS performs well out of the box but can be tuned for high-traffic deployments.
Thread configuration:
# Number of threads receiving packets (set to number of CPU cores)
receiver-threads=4
# Number of threads distributing queries to backends
distributor-threads=4
# Signing threads for DNSSEC
signing-threads=3
Cache settings:
# Query cache: caches full DNS responses
query-cache-ttl=20
# Packet cache: caches entire DNS packets (very effective)
cache-ttl=60
# Negative cache TTL
negquery-cache-ttl=60
Database connection pooling:
# MySQL: keep connections alive
gmysql-timeout=10
# PostgreSQL: connection timeout
gpgsql-extra-connection-parameters=connect_timeout=5
Socket options:
# Reuse port (kernel load-balances across threads)
reuseport=yes
System-level tuning:
# Increase max open files
cat >> /etc/security/limits.d/pdns.conf << 'EOF'
pdns soft nofile 65536
pdns hard nofile 65536
EOF
# Increase socket receive buffer
sysctl -w net.core.rmem_max=8388608
sysctl -w net.core.wmem_max=8388608
# Add to /etc/sysctl.conf for persistence
echo 'net.core.rmem_max=8388608' >> /etc/sysctl.conf
echo 'net.core.wmem_max=8388608' >> /etc/sysctl.conf
Monitor the cache hit ratio with the statistics API to determine if your cache TTL values are effective. A hit ratio above 80% is good for most workloads.
17. High availability configuration
Two main approaches: DNS-level replication (zone transfers) and database-level replication.
Approach 1: Zone transfers (MASTER/SLAVE)
On the primary server, configure zones as MASTER:
pdnsutil create-zone example.com ns1.example.com
pdnsutil set-kind example.com master
pdnsutil set-meta example.com ALLOW-AXFR-FROM 192.168.1.11/32
In pdns.conf on the primary:
allow-axfr-ips=192.168.1.11/32
also-notify=192.168.1.11
master=yes
On the secondary server, configure the zone as SLAVE:
pdnsutil create-slave-zone example.com 192.168.1.10
In pdns.conf on the secondary:
slave=yes
slave-cycle-interval=60
The secondary will AXFR the zone from the primary and keep it updated via NOTIFY messages.
Approach 2: Database replication (NATIVE zones)
Use MySQL/MariaDB Galera Cluster or PostgreSQL streaming replication. All PowerDNS nodes connect to the database cluster and serve identical data. Zones use type NATIVE since replication happens at the database layer.
pdnsutil set-kind example.com native
This approach is simpler to manage at scale (no zone transfer configuration per zone) but requires a reliable database cluster.
DNS load balancing
Publish multiple NS records pointing to different servers. DNS clients will round-robin between them. For active health checking, put dnsdist in front:
# dnsdist.conf
newServer({address="192.168.1.10:5300", name="pdns1", checkName="health.example.com."})
newServer({address="192.168.1.11:5300", name="pdns2", checkName="health.example.com."})
18. Monitoring DNS traffic
PowerDNS exposes detailed statistics via multiple interfaces.
pdns_control (CLI):
# All statistics
pdns_control show '*'
# Specific counters
pdns_control show udp-queries
pdns_control show tcp-queries
pdns_control show cache-hits
pdns_control show cache-misses
pdns_control show query-cache-hit
pdns_control show query-cache-miss
pdns_control show servfail-packets
pdns_control show timedout-packets
REST API statistics:
curl -s -H 'X-API-Key: YOUR_SECRET_API_KEY_HERE' \
http://127.0.0.1:8081/api/v1/servers/localhost/statistics | python3 -m json.tool
Carbon/Graphite export: PowerDNS can push metrics directly to a Carbon/Graphite server:
carbon-server=10.0.0.50:2003
carbon-interval=30
carbon-ourname=pdns-auth-01
Prometheus: Use the webserver statistics endpoint and a Prometheus exporter, or scrape the API. The powerdns_exporter project on GitHub translates the stats into Prometheus format.
Packet-level monitoring:
# Capture DNS traffic with tcpdump
tcpdump -i eth0 -n port 53 -w /tmp/dns-capture.pcap
# Real-time query monitoring with dnstop
dnstop eth0
# Deep analysis with dnscap
dnscap -i eth0 -w /tmp/dns-
Key metrics to alert on: servfail-packets (backend errors), timedout-packets (database latency), udp-do-queries (DNSSEC-aware client ratio), and signature-cache-hit vs signature-cache-miss (DNSSEC signing load).
19. Testing DNS queries
Always verify your zones respond correctly before relying on them in production.
dig (the standard DNS testing tool — see also our dig command guide):
# Query your server directly
dig @192.168.1.10 example.com A
dig @192.168.1.10 example.com SOA
dig @192.168.1.10 example.com NS
dig @192.168.1.10 www.example.com AAAA
# Check DNSSEC
dig @192.168.1.10 example.com DNSKEY +dnssec
dig @192.168.1.10 example.com A +dnssec +multi
# Zone transfer
dig @192.168.1.10 example.com AXFR
# Short output
dig @192.168.1.10 example.com A +short
pdnsutil zone checks:
# Validate zone data
pdnsutil check-zone example.com
# Check all zones
pdnsutil check-all-zones
# List zone contents
pdnsutil list-zone example.com
# Export as BIND zone file
pdnsutil list-zone example.com > /tmp/example.com.zone
drill (LDNS-based alternative to dig):
drill @192.168.1.10 example.com A
drill @192.168.1.10 -D example.com A # DNSSEC validation
Test from external perspective (after DNS is public):
# Use a public resolver to confirm delegation works
dig @8.8.8.8 example.com A
dig @1.1.1.1 example.com A
# Check DNSSEC chain of trust
dig example.com A +trace +dnssec
Verify that your SOA serial increments after record changes, that NS records point to reachable servers, and that AXFR works from your secondary servers.
20. Troubleshooting PowerDNS
PowerDNS won't start:
# Check the journal for errors
journalctl -u pdns --no-pager -n 50
# Run in foreground for immediate error output
pdns_server --daemon=no --guardian=no --loglevel=9
Common causes: backend connection failure (wrong credentials, database not running), port 53 already in use (check with ss -tlnup | grep :53), or config syntax errors.
Port 53 already in use:
# Find what's using port 53
ss -tlnup | grep :53
# On systemd systems, systemd-resolved often binds 127.0.0.53:53
# Disable it:
systemctl stop systemd-resolved
systemctl disable systemd-resolved
rm /etc/resolv.conf
echo "nameserver 8.8.8.8" > /etc/resolv.conf
Queries return SERVFAIL:
- Backend database is unreachable. Check with
pdns_control backend-cmd gmysql 'SELECT 1' - Zone data is invalid. Run
pdnsutil check-zone example.com - DNSSEC is broken. Check with
pdnsutil show-zone example.comand verify keys are active
Zone transfers fail:
# Check if AXFR is allowed
pdns_control list-zones --type=master
# Verify allow-axfr-ips includes the secondary's IP
grep allow-axfr-ips /etc/powerdns/pdns.conf
# Test AXFR manually
dig @192.168.1.10 example.com AXFR
Useful pdns_control commands:
pdns_control ping # Check if daemon is responsive
pdns_control rping # Check backend responsiveness
pdns_control version # Show version
pdns_control purge example.com # Clear cache for a domain
pdns_control reload # Reload configuration (not all settings)
pdns_control rediscover # Re-read zone list from backend
pdns_control retrieve example.com # Force re-transfer of slave zone
Slow queries: Enable query logging temporarily, check database slow query log, verify indexes exist on the records table (domain_id, name, type).
21. Backup and maintenance
Database backup (MySQL):
# Full dump
mysqldump -u pdns -p pdns > /backup/pdns-$(date +%Y%m%d).sql
# Compressed
mysqldump -u pdns -p pdns | gzip > /backup/pdns-$(date +%Y%m%d).sql.gz
Database backup (PostgreSQL):
pg_dump -U pdns pdns > /backup/pdns-$(date +%Y%m%d).sql
pg_dump -U pdns -Fc pdns > /backup/pdns-$(date +%Y%m%d).dump
Zone file export (portable, backend-independent):
# Export all zones as BIND zone files
mkdir -p /backup/zones
for zone in $(pdnsutil list-all-zones); do
pdnsutil list-zone "$zone" > "/backup/zones/${zone}.zone"
done
tar czf /backup/zones-$(date +%Y%m%d).tar.gz -C /backup zones/
DNSSEC key backup: If you use the database backend for keys, they are included in the database dump. For file-based key storage, back up /etc/powerdns/keys/ or wherever key-directory points.
Automated backup cron job:
cat > /etc/cron.d/pdns-backup << 'EOF'
0 2 * * * root mysqldump -u pdns -p'PASSWORD' pdns | gzip > /backup/pdns-$(date +\%Y\%m\%d).sql.gz
0 3 * * 0 root find /backup -name 'pdns-*.sql.gz' -mtime +30 -delete
EOF
Routine maintenance tasks:
# Check all zones for errors
pdnsutil check-all-zones
# Rectify all zones (DNSSEC)
for zone in $(pdnsutil list-all-zones); do
pdnsutil rectify-zone "$zone"
done
# Optimize MySQL tables (periodically, during low traffic)
mysqlcheck -u pdns -p --optimize pdns
Upgrades: Before upgrading PowerDNS, back up the database and configuration. Check the release notes for schema changes. After upgrading the package, run any required schema migrations:
# Check current schema version
pdnsutil backend-cmd gmysql 'SELECT value FROM domainmetadata WHERE kind="SCHEMA-VERSION"'
# Apply migrations if needed (check /usr/share/doc/pdns-backend-mysql/)
Frequently Asked Questions
What database backends does PowerDNS support?
PowerDNS supports MySQL/MariaDB (gmysql), PostgreSQL (gpgsql), SQLite 3 (gsqlite3), LMDB, BIND zone files, and several others. The gmysql and gpgsql backends are the most widely deployed in production. LMDB is the fastest option for read-heavy workloads but does not support all features. The BIND backend reads traditional zone files, which can be useful during migrations.
Can PowerDNS replace BIND?
Yes. PowerDNS handles all standard authoritative DNS functions: zone hosting, AXFR/IXFR transfers, DNSSEC, ALIAS records, and more. It lacks the built-in recursive resolver that BIND bundles, but the separate pdns_recursor fills that role. Many large hosting providers migrated from BIND to PowerDNS specifically for the database backend and API support.
How does PowerDNS handle DNSSEC?
PowerDNS performs online signing at query time. Run pdnsutil secure-zone to generate keys and enable signing. It supports Algorithm 13 (ECDSAP256SHA256) and Algorithm 8 (RSASHA256). Key rollovers are handled with pdnsutil. You still need to submit DS records to your parent zone registrar manually or via CDS/CDNSKEY automation if the parent supports it.
Is the PowerDNS API production-ready?
Yes. The REST API has been stable since PowerDNS 4.x and is used in production by hosting platforms serving millions of zones. It supports full CRUD operations on zones and records, DNSSEC key management, zone metadata, and server statistics. Protect it with a strong API key and firewall rules.
How do I migrate zones from BIND to PowerDNS?
Use the zone2sql utility included with PowerDNS. It reads BIND zone files and outputs SQL INSERT statements for your chosen backend. Run zone2sql --gmysql --zone=example.com.zone --zone-name=example.com | mysql -u pdns pdns. Alternatively, configure PowerDNS as a secondary to your BIND server and let it AXFR all zones, then promote them to primary.
What is the difference between NATIVE, MASTER, and SLAVE zone types?
MASTER zones send NOTIFY messages and serve AXFR to secondaries. SLAVE zones are fetched from a remote master and updated via AXFR/IXFR. NATIVE zones are managed directly in the database without any replication; useful when you replicate at the database layer (e.g., MySQL replication or Galera cluster) instead of using DNS zone transfers.
How many queries per second can PowerDNS handle?
On modern hardware with the gmysql backend, PowerDNS comfortably handles 50,000-100,000 queries per second. With the LMDB backend, throughput exceeds 200,000 qps. Performance depends on record count, query patterns, and hardware. Increase receiver-threads and distributor-threads to scale across CPU cores.
For related tools and guides, see our BIND setup guide, the dig command reference, or browse the public DNS server directory.