Poweradmin is a web-based DNS administration interface for PowerDNS. This guide provides comprehensive security hardening recommendations for production deployments.
Poweradmin manages critical DNS infrastructure and should be secured as a privileged application. Key security considerations include:
Always use HTTPS for Poweradmin access:
# Generate self-signed certificate (testing only)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/poweradmin.key \
-out /etc/ssl/certs/poweradmin.crt \
-subj "/CN=dns-admin.example.com"
Production: Use certificates from Let’s Encrypt or your CA:
# Install Certbot
sudo apt install certbot python3-certbot-apache # or python3-certbot-nginx
# Obtain certificate
sudo certbot --apache -d dns-admin.example.com
# or
sudo certbot --nginx -d dns-admin.example.com
Nginx HTTPS Configuration:
server {
listen 443 ssl http2;
server_name dns-admin.example.com;
ssl_certificate /etc/letsencrypt/live/dns-admin.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dns-admin.example.com/privkey.pem;
# Modern SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# Security headers
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name dns-admin.example.com;
return 301 https://$server_name$request_uri;
}
Apache HTTPS Configuration:
<VirtualHost *:443>
ServerName dns-admin.example.com
DocumentRoot /var/www/poweradmin/web
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/dns-admin.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/dns-admin.example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/dns-admin.example.com/chain.pem
# Modern SSL configuration
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
SSLSessionTickets off
# Security headers
Header always set Strict-Transport-Security "max-age=63072000"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
<Directory /var/www/poweradmin/web>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Enforce strong passwords in Poweradmin configuration:
<?php
// config.inc.php
$password_min_length = 12;
$password_require_uppercase = true;
$password_require_lowercase = true;
$password_require_numbers = true;
$password_require_special = true;
$password_history_count = 5; // Prevent reuse of last 5 passwords
$password_max_age = 7776000; // Force change every 90 days
?>
Enable TOTP-based two-factor authentication:
<?php
$mfa_enabled = true;
$mfa_required_for_roles = ['admin', 'superuser'];
$mfa_optional_for_roles = ['user'];
$mfa_trusted_devices = true;
$mfa_trusted_device_lifetime = 2592000; // 30 days
?>
User Setup:
Secure session handling:
<?php
// Session configuration
$session_timeout = 3600; // 1 hour inactivity timeout
$session_secure = true; // HTTPS only
$session_httponly = true; // No JavaScript access
$session_samesite = 'Strict'; // CSRF protection
// Additional session hardening
ini_set('session.cookie_secure', '1');
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_strict_mode', '1');
ini_set('session.use_only_cookies', '1');
?>
Protect against brute-force attacks:
<?php
// Account lockout settings
$lockout_enabled = true;
$lockout_threshold = 5; // Failed attempts before lockout
$lockout_duration = 900; // Lockout duration (15 minutes)
$lockout_notify_admin = true;
?>
Deploy Poweradmin on a separate host from PowerDNS authoritative servers:
┌─────────────────┐ ┌──────────────────┐
│ Poweradmin UI │ ──────► │ PowerDNS Auth │
│ (10.0.1.10) │ API │ (10.0.2.10-20) │
└─────────────────┘ └──────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ MariaDB │ │ MariaDB │
│ (Poweradmin) │ │ (PowerDNS) │
└─────────────────┘ └──────────────────┘
Benefits:
Configure database to only accept connections from Poweradmin host:
MySQL/MariaDB:
-- Create user restricted to Poweradmin host
CREATE USER 'poweradmin'@'10.0.1.10' IDENTIFIED BY 'secure_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON poweradmin.* TO 'poweradmin'@'10.0.1.10';
FLUSH PRIVILEGES;
-- For PowerDNS read access
CREATE USER 'poweradmin'@'10.0.1.10' IDENTIFIED BY 'secure_password';
GRANT SELECT ON pdns.* TO 'poweradmin'@'10.0.1.10';
FLUSH PRIVILEGES;
Firewall Rules:
# Allow MySQL only from Poweradmin host
sudo ufw allow from 10.0.1.10 to any port 3306 proto tcp
# Deny all other MySQL access
sudo ufw deny 3306/tcp
Restrict PowerDNS API access:
PowerDNS Configuration (/etc/powerdns/pdns.conf):
# API settings
api=yes
api-key=your_very_secure_api_key_here
# Restrict API to localhost or specific IPs
webserver-address=127.0.0.1
webserver-port=8081
# Or bind to specific interface
# webserver-address=10.0.1.10
# Optional: Additional access control
allow-axfr-ips=10.0.1.10
allow-notify-from=10.0.1.10
Firewall Rules:
# Allow PowerDNS API only from Poweradmin host
sudo ufw allow from 10.0.1.10 to any port 8081 proto tcp
sudo ufw deny 8081/tcp
Create separate database users with minimal required permissions:
-- Poweradmin user (application database)
CREATE USER 'poweradmin_app'@'localhost' IDENTIFIED BY 'secure_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON poweradmin.* TO 'poweradmin_app'@'localhost';
-- Poweradmin admin user (for schema changes)
CREATE USER 'poweradmin_admin'@'localhost' IDENTIFIED BY 'very_secure_password';
GRANT ALL PRIVILEGES ON poweradmin.* TO 'poweradmin_admin'@'localhost';
-- PowerDNS read-only user for Poweradmin
CREATE USER 'poweradmin_pdns'@'localhost' IDENTIFIED BY 'secure_password';
GRANT SELECT ON pdns.domains TO 'poweradmin_pdns'@'localhost';
GRANT SELECT ON pdns.records TO 'poweradmin_pdns'@'localhost';
GRANT SELECT ON pdns.cryptokeys TO 'poweradmin_pdns'@'localhost';
Configure comprehensive audit logging:
<?php
// Audit configuration
$audit_enabled = true;
$audit_log_zone_changes = true;
$audit_log_user_changes = true;
$audit_log_login_attempts = true;
$audit_log_api_calls = true;
$audit_log_configuration_changes = true;
// Log retention
$audit_retention_days = 365;
// Log destination
$audit_log_file = '/var/log/poweradmin/audit.log';
$audit_syslog = true;
$audit_syslog_facility = LOG_AUTH;
?>
Alert on mass record changes:
<?php
// Zone change monitoring
$zone_change_alerts = true;
$zone_change_threshold = 10; // Alert if more than 10 records changed at once
$zone_change_notify_roles = ['admin', 'superuser'];
$zone_change_notify_email = 'dns-alerts@example.com';
?>
Example audit log entry:
2026-02-22 14:32:15 [AUDIT] user=admin action=zone_update zone=example.com
changes=5 records=A:www,AAAA:www,MX:@,TXT:@,CNAME:mail
source_ip=10.0.1.50 session_id=abc123
Track user actions:
-- Example audit table structure
CREATE TABLE audit_log (
id INT AUTO_INCREMENT PRIMARY KEY,
timestamp DATETIME NOT NULL,
user_id INT NOT NULL,
username VARCHAR(255) NOT NULL,
action VARCHAR(50) NOT NULL,
resource_type VARCHAR(50),
resource_id INT,
resource_name VARCHAR(255),
old_value TEXT,
new_value TEXT,
source_ip VARCHAR(45),
user_agent VARCHAR(255),
session_id VARCHAR(64),
INDEX idx_user (user_id),
INDEX idx_timestamp (timestamp),
INDEX idx_action (action)
);
Set up alerts for security events:
<?php
// Alert configuration
$alerts_enabled = true;
$alert_email = 'security@example.com';
$alert_slack_webhook = 'https://hooks.slack.com/services/xxx/yyy/zzz';
// Alert triggers
$alert_on_failed_login = true;
$alert_on_failed_login_threshold = 5;
$alert_on_privilege_escalation = true;
$alert_on_zone_deletion = true;
$alert_on_api_key_usage = true;
$alert_on_user_creation = true;
$alert_on_password_change = true;
?>
Define roles with specific permissions:
<?php
// Role definitions
$roles = [
'superuser' => [
'description' => 'Full system access',
'permissions' => ['*'],
],
'admin' => [
'description' => 'Administrative access',
'permissions' => [
'zones:*',
'users:view',
'users:create',
'users:edit',
'templates:*',
'logs:view',
'settings:view',
],
],
'operator' => [
'description' => 'DNS operations',
'permissions' => [
'zones:view',
'zones:edit',
'records:*',
'templates:view',
],
],
'viewer' => [
'description' => 'Read-only access',
'permissions' => [
'zones:view',
'records:view',
'templates:view',
],
],
];
?>
Restrict access by IP address:
<?php
// Allowed IP ranges
$allowed_ips = [
'10.0.1.0/24', // Admin network
'192.168.100.0/24', // Management network
];
// Deny specific IPs
$denied_ips = [
'203.0.113.0/24', // Known bad actors
];
// Require MFA for external IPs
$mfa_required_for_external = true;
$internal_networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'];
?>
Apache IP Restrictions:
<Directory /var/www/poweradmin/web>
Require all granted
Require ip 10.0.1.0/24
Require ip 192.168.100.0/24
</Directory>
Nginx IP Restrictions:
location / {
allow 10.0.1.0/24;
allow 192.168.100.0/24;
deny all;
proxy_pass http://127.0.0.1:8080;
}
Restrict access to business hours:
<?php
// Time-based access (optional)
$time_restrictions_enabled = true;
$allowed_hours_start = 6; // 6 AM
$allowed_hours_end = 22; // 10 PM
$allowed_days = [1, 2, 3, 4, 5]; // Monday-Friday
// Emergency access override
$emergency_access_enabled = true;
$emergency_access_roles = ['superuser', 'admin'];
?>
Set secure file permissions:
# Set ownership
sudo chown -R www-data:www-data /var/www/poweradmin
# Set directory permissions
sudo find /var/www/poweradmin -type d -exec chmod 755 {} \;
# Set file permissions
sudo find /var/www/poweradmin -type f -exec chmod 644 {} \;
# Protect config file
sudo chmod 640 /var/www/poweradmin/config.inc.php
sudo chown root:www-data /var/www/poweradmin/config.inc.php
# Secure var directory
sudo chmod 775 /var/www/poweradmin/var
sudo chmod 770 /var/www/poweradmin/var/cache
sudo chmod 770 /var/www/poweradmin/var/log
Configure PHP security settings:
php.ini:
[PHP]
; Disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
; Hide PHP version
expose_php = Off
; Error handling
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log
; File uploads
file_uploads = Off
upload_max_filesize = 1M
max_file_uploads = 5
; Session security
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = Strict
session.use_strict_mode = 1
session.use_only_cookies = 1
; Open basedir restriction
open_basedir = /var/www/poweradmin:/tmp
Apache Security Modules:
# Enable security modules
sudo a2enmod headers
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod security2 # If mod_security installed
# Disable server signature
echo "ServerTokens Prod" | sudo tee -a /etc/apache2/conf-available/security.conf
echo "ServerSignature Off" | sudo tee -a /etc/apache2/conf-available/security.conf
Nginx Security Headers:
# Add to server block
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self';" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
MySQL/MariaDB Security:
# Run secure installation
sudo mysql_secure_installation
-- Remove anonymous users
DELETE FROM mysql.user WHERE User='';
-- Remove test database
DROP DATABASE IF EXISTS test;
-- Restrict remote root access
UPDATE mysql.user SET Host='localhost' WHERE User='root';
FLUSH PRIVILEGES;
-- Create dedicated user for Poweradmin
CREATE USER 'poweradmin'@'localhost' IDENTIFIED BY 'secure_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON poweradmin.* TO 'poweradmin'@'localhost';
Audit user accounts periodically:
#!/bin/bash
# review_users.sh
echo "=== Poweradmin User Review ==="
echo "Date: $(date)"
echo ""
# List all users
mysql -u root -p poweradmin -e "SELECT id, username, email, role, created, last_login FROM users ORDER BY id;"
# Find inactive users (90+ days)
echo ""
echo "=== Inactive Users (90+ days) ==="
mysql -u root -p poweradmin -e "SELECT username, email, last_login FROM users WHERE last_login < DATE_SUB(NOW(), INTERVAL 90 DAY);"
# Find users with weak passwords (if password history enabled)
echo ""
echo "=== Password Age Review ==="
mysql -u root -p poweradmin -e "SELECT username, password_changed FROM users WHERE password_changed < DATE_SUB(NOW(), INTERVAL 90 DAY);"
Secure your backups:
#!/bin/bash
# secure_backup.sh
BACKUP_DIR="/var/backups/poweradmin"
DATE=$(date +%Y%m%d_%H%M%S)
ENCRYPTION_KEY="/etc/poweradmin/backup.key"
# Create backup
mysqldump -u root poweradmin | gzip > $BACKUP_DIR/poweradmin_$DATE.sql.gz
# Encrypt backup
openssl enc -aes-256-cbc -salt -pbkdf2 -in $BACKUP_DIR/poweradmin_$DATE.sql.gz -out $BACKUP_DIR/poweradmin_$DATE.sql.gz.enc -pass file:$ENCRYPTION_KEY
# Remove unencrypted backup
rm $BACKUP_DIR/poweradmin_$DATE.sql.gz
# Set secure permissions
chmod 600 $BACKUP_DIR/poweradmin_$DATE.sql.gz.enc
chown root:root $BACKUP_DIR/poweradmin_$DATE.sql.gz.enc
# Upload to secure storage (example with rsync)
# rsync -avz $BACKUP_DIR/poweradmin_$DATE.sql.gz.enc backup-server:/secure-backups/
# Retention - keep only last 30 days
find $BACKUP_DIR -name "*.enc" -mtime +30 -delete
Keep Poweradmin and dependencies updated:
#!/bin/bash
# update_poweradmin.sh
POWERADMIN_DIR="/var/www/poweradmin"
BACKUP_DIR="/var/backups/poweradmin"
# Create backup before update
mysqldump -u root poweradmin | gzip > $BACKUP_DIR/poweradmin_pre_update_$(date +%Y%m%d).sql.gz
# Update Poweradmin
cd $POWERADMIN_DIR
git fetch origin
git checkout v4.0.7 # Or latest stable
git pull origin release/4.0.x
# Clear cache
rm -rf $POWERADMIN_DIR/var/cache/*
# Restart web server
systemctl restart apache2 # or nginx
echo "Update completed. Please verify functionality."
Set up monitoring for security events:
Fail2ban Configuration (/etc/fail2ban/jail.local):
[poweradmin]
enabled = true
port = http,https
filter = poweradmin
logpath = /var/log/poweradmin/*.log
maxretry = 5
bantime = 3600
findtime = 600
Fail2ban Filter (/etc/fail2ban/filter.d/poweradmin.conf):
[Definition]
failregex = ^.*Failed login attempt for user.*from <HOST>.*$
^.*Authentication failed.*IP: <HOST>.*$
ignoreregex =
Concerned about your DNS infrastructure security? We provide security audits, penetration testing, and hardening services for Poweradmin and PowerDNS deployments. Reach out to office@linux-server-admin.com or visit our contact page.