WordPress Security Hardening
WordPress security is critical given its market dominance and constant targeting by attackers. Default installations expose multiple vulnerabilities including weak authentication, information disclosure, SQL injection, and file access issues. Comprehensive hardening requires securing file permissions, protecting configuration files, strengthening authentication, implementing Web Application Firewalls, regular scanning, hardening HTTP headers, enabling automatic updates, and monitoring for suspicious activity. This guide covers production-ready security practices protecting your WordPress installation from common and advanced attack vectors.
Table of Contents
- WordPress Security Landscape
- File Permissions and Ownership
- WordPress Configuration Security
- Authentication Hardening
- Access Control and WAF
- Security Scanning and Monitoring
- HTTP Security Headers
- Automatic Updates
- Backup and Disaster Recovery
- Conclusion
WordPress Security Landscape
WordPress security threats include authentication attacks, injection vulnerabilities, malware, and data theft. Understanding threats enables proper mitigation.
Common WordPress attack vectors:
- Brute force login attacks
- Plugin/theme vulnerabilities
- SQL injection via unpatched plugins
- File upload exploits
- Weak credentials
- Outdated core/plugins/themes
- Information disclosure
- Cross-site scripting (XSS)
- Cross-site request forgery (CSRF)
Security layers for defense:
- Network layer (firewall, WAF)
- Application layer (authentication, input validation)
- File system layer (permissions, ownership)
- Database layer (SQL injection prevention)
- Code level (secure coding practices)
Check WordPress version:
grep -i "wp_version = " /var/www/wordpress/wp-includes/version.php
Identify installed plugins and themes:
# List plugins
ls -la /var/www/wordpress/wp-content/plugins/
# List themes
ls -la /var/www/wordpress/wp-content/themes/
# Check via WP-CLI
wp plugin list
wp theme list
File Permissions and Ownership
Incorrect permissions are a common vulnerability enabling unauthorized access and modification.
Verify current permissions:
# Check WordPress directory
ls -ld /var/www/wordpress
ls -la /var/www/wordpress/ | head -20
Set secure file ownership:
# WordPress files should be owned by www-data
sudo chown -R www-data:www-data /var/www/wordpress
# Verify ownership
ls -ld /var/www/wordpress
Set restrictive directory permissions:
# All directories should be 755 (readable, not writable by others)
sudo find /var/www/wordpress -type d -exec chmod 755 {} \;
# Verify
find /var/www/wordpress -type d -perm /077 -ls | head -10
Set restrictive file permissions:
# All files should be 644 (readable, not writable by others)
sudo find /var/www/wordpress -type f -exec chmod 644 {} \;
# Verify
find /var/www/wordpress -type f -perm /077 -ls | head -10
Set special permissions for writable directories:
# These directories must be writable by web server
sudo chmod 755 /var/www/wordpress/wp-content
sudo chmod 755 /var/www/wordpress/wp-content/uploads
sudo chmod 755 /var/www/wordpress/wp-content/plugins
sudo chmod 755 /var/www/wordpress/wp-content/themes
sudo chmod 755 /var/www/wordpress/wp-content/cache
# Avoid world-writable permissions
find /var/www/wordpress -perm /002 -ls
Disable file editing in WordPress:
# Add to wp-config.php
sudo nano /var/www/wordpress/wp-config.php
# Add before "That's all, stop editing":
define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', true);
WordPress Configuration Security
Secure WordPress configuration prevents information disclosure and unauthorized access.
Secure wp-config.php file:
# Move wp-config.php out of web root (if possible)
sudo mv /var/www/wordpress/wp-config.php /var/www/wordpress/../
# Update WordPress to reference new location
# WordPress will automatically find it in parent directory
# Verify
ls -la /var/www/wordpress/wp-config.php
ls -la /var/www/wp-config.php
If moving wp-config.php isn't possible, protect via web server:
# Nginx configuration
sudo nano /etc/nginx/sites-available/wordpress.conf
# Add to server block:
location ~ /wp-config.php$ {
deny all;
}
# Test and reload
sudo nginx -t
sudo systemctl reload nginx
Hide WordPress version:
# Remove from header
sudo nano /var/www/wordpress/wp-config.php
# Add:
define('ABSPATH', dirname(__FILE__) . '/');
# Remove from wp-includes/version.php header output
Disable XML-RPC if not needed:
# Add to wp-config.php
define('XMLRPC_REQUEST', false);
Or disable via Nginx:
location = /xmlrpc.php {
deny all;
}
Remove default WordPress database prefix:
# Change wp_* to custom prefix like wp8f3_*
# Must be done during installation or with migration tool
Set proper secret keys:
# Generate at https://api.wordpress.org/secret-key/1.1/salt/
# Paste in wp-config.php replacing default keys
curl https://api.wordpress.org/secret-key/1.1/salt/
Configure authentication methods:
// In wp-config.php
// Disable password reset
define('ALLOW_PASSWORD_RESET', false); // Only if needed
// Force HTTPS for all admin pages
define('FORCE_SSL_ADMIN', true);
define('FORCE_SSL_LOGIN', true);
// Limit login attempts
define('ABSPATH', dirname(__FILE__) . '/');
Authentication Hardening
Weak authentication is a primary attack vector. Strengthen login security extensively.
Change default admin username:
# Change via database if user ID 1 is 'admin'
mysql -u root -p wordpress_db << EOF
UPDATE wp_users SET user_login='newadmin' WHERE ID=1;
EOF
# Or create new admin and delete old
wp user create newadmin [email protected] --role=administrator --user-pass=SecurePassword123!
wp user delete admin --reassign=2
Enforce strong passwords:
# Install plugin enforcing strong passwords
wp plugin install password-strength-meter --activate
# Or set via wp-config.php
define('WP_PASSWORD_STRENGTH', 4);
Enable two-factor authentication:
# Install 2FA plugin
wp plugin install two-factor --activate
# Or
wp plugin install duo-wordpress-plugin --activate
# Configure in admin: Security > Two-Factor Authentication
Limit login attempts:
# Install and configure
wp plugin install limit-login-attempts-reloaded --activate
# Via plugin settings:
# Max login attempts: 5
# Lockout duration: 30 minutes
# Log IP addresses
Or configure via Nginx:
# Rate limiting for login page
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
location = /wp-login.php {
limit_req zone=login burst=5 nodelay;
fastcgi_pass php_backend;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
Disable user enumeration:
# Install plugin
wp plugin install hide-users --activate
# Or configure via Nginx
location ~* ^/wp-json/wp/v2/users/ {
deny all;
}
Access Control and WAF
Control who can access WordPress and block malicious requests.
Install ModSecurity WAF:
# Install ModSecurity
sudo apt install libmodsecurity3 libmodsecurity-dev -y
# Install ModSecurity for Nginx (via compilation or as package)
# Note: Nginx requires recompilation with ModSecurity module
Or use cloud WAF services:
# Cloudflare Web Application Firewall
# Sucuri Firewall
# Wordfence Security
# Install Wordfence (easiest for WordPress)
wp plugin install wordfence --activate
Configure Wordfence firewall:
# Access Wordfence settings in WordPress admin
# Configure:
# - Block known malicious IPs
# - Enable real-time threat intelligence
# - Configure rate limiting
# - Set up alerts
Restrict admin access by IP:
# Nginx configuration
geo $blocked_admin {
default 1;
203.0.113.0/24 0; # Your office IP range
198.51.100.0/24 0; # VPN IP range
}
server {
location ~ ^/wp-admin/ {
if ($blocked_admin) {
return 403;
}
}
location = /wp-login.php {
if ($blocked_admin) {
return 403;
}
}
}
Change default login URL:
# Install plugin
wp plugin install wps-hide-login --activate
# Configure new login URL (e.g., /my-private-login/)
# Old URL redirects to homepage
Block common attack paths:
# Block potentially dangerous patterns
location ~* "(/wp-admin/includes/|/wp-includes/|wp-config\.php|wp-settings\.php)" {
deny all;
}
# Block script uploads to uploads directory
location ~* "^/wp-content/uploads/.*\.php" {
deny all;
}
# Block access to .htaccess and other config files
location ~* "\.(htaccess|htpasswd|config|env)$" {
deny all;
}
Require HTTPS for all traffic:
# Redirect HTTP to HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
# Force HTTPS throughout site
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
Configure in WordPress:
# Force HTTPS in wp-config.php
define('FORCE_SSL_ADMIN', true);
define('FORCE_SSL_LOGIN', true);
define('WP_HOME', 'https://example.com');
define('WP_SITEURL', 'https://example.com');
Security Scanning and Monitoring
Regular scanning identifies vulnerabilities before attackers exploit them.
Install security scanning plugin:
# Wordfence Scan
wp plugin install wordfence --activate
# Sucuri Security
wp plugin install sucuri-scanner --activate
# iThemes Security
wp plugin install better-wp-security --activate
Configure automated scanning:
# Via Wordfence admin:
# Set scanning frequency: Daily or Weekly
# Enable malware scanning
# Enable core file integrity checking
# Enable plugin/theme vulnerability scanning
Monitor file changes:
# Create script to monitor WordPress file integrity
cat > /usr/local/bin/wordpress-integrity-check.sh << 'EOF'
#!/bin/bash
WP_PATH="/var/www/wordpress"
HASH_FILE="/var/lib/wordpress-hashes.txt"
# Generate hashes on first run
if [ ! -f "$HASH_FILE" ]; then
find "$WP_PATH/wp-includes" "$WP_PATH/wp-admin" -type f -name "*.php" | while read file; do
echo "$(md5sum "$file")" >> "$HASH_FILE"
done
exit 0
fi
# Check for changes
find "$WP_PATH/wp-includes" "$WP_PATH/wp-admin" -type f -name "*.php" | while read file; do
current_hash=$(md5sum "$file" | awk '{print $1}')
stored_hash=$(grep "$file" "$HASH_FILE" | awk '{print $1}')
if [ "$current_hash" != "$stored_hash" ]; then
echo "ALERT: File modified: $file" | mail -s "WordPress Integrity Alert" [email protected]
fi
done
EOF
sudo chmod +x /usr/local/bin/wordpress-integrity-check.sh
Schedule integrity checks:
sudo crontab -e
# Daily integrity check
0 2 * * * /usr/local/bin/wordpress-integrity-check.sh
Monitor login attempts:
# View login attempts in logs
sudo tail -f /var/log/nginx/wordpress_access.log | grep wp-login
# Count failed attempts
grep "wp-login" /var/log/nginx/wordpress_access.log | grep " 200 " | wc -l
Monitor database changes:
# Create database backup for comparison
mysqldump -u root -p wordpress_db > /backups/wordpress-db-baseline.sql
# Regular verification
mysqldump -u root -p wordpress_db > /backups/wordpress-db-current.sql
diff /backups/wordpress-db-baseline.sql /backups/wordpress-db-current.sql
HTTP Security Headers
HTTP headers provide browser-level security protections.
Configure security headers in Nginx:
# 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 Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Strict Transport Security (HSTS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Content Security Policy (stricter for WordPress)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.googleapis.com *.gstatic.com; style-src 'self' 'unsafe-inline' *.googleapis.com; img-src 'self' data: https:; font-src 'self' data:;" always;
Test security headers:
# Check headers
curl -I https://example.com/
# Should show all security headers
# Use online tools: https://securityheaders.com
Automatic Updates
Enable automatic updates for WordPress core, plugins, and themes.
Configure automatic updates in wp-config.php:
// Enable automatic updates
define('AUTOMATIC_UPDATER_DISABLED', false);
// Core updates
define('WP_AUTO_UPDATE_CORE', true);
// Plugin updates
add_filter('auto_update_plugin', '__return_true');
// Theme updates
add_filter('auto_update_theme', '__return_true');
// Disable updates for specific plugins
add_filter('auto_update_plugin', function($update, $item) {
if ('critical-plugin' === $item->slug) {
return false; // Don't auto-update
}
return $update;
}, 10, 2);
Configure email notifications:
// Email on update completion
add_action('automatic_updates_complete', function($update_results) {
$message = "WordPress Updates Completed:\n\n";
if (!empty($update_results['core'])) {
$message .= "Core updated\n";
}
if (!empty($update_results['plugins'])) {
$message .= "Plugins updated: " . count($update_results['plugins']) . "\n";
}
if (!empty($update_results['themes'])) {
$message .= "Themes updated: " . count($update_results['themes']) . "\n";
}
wp_mail(get_option('admin_email'), 'WordPress Updates Completed', $message);
});
Monitor updates:
# Check for available updates via WP-CLI
wp core check-update
wp plugin update-list
wp theme update-list
# Check update logs
tail -f /var/www/wordpress/wp-content/debug.log
Enable debug logging:
// In wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
// Logs to wp-content/debug.log
Backup and Disaster Recovery
Regular backups enable recovery from security incidents.
Create automated backup script:
cat > /usr/local/bin/wordpress-backup.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/backups/wordpress"
WP_PATH="/var/www/wordpress"
DB_NAME="wordpress_db"
DB_USER="wordpress_user"
DB_PASS="SecurePassword123!"
mkdir -p "$BACKUP_DIR"
# Backup database
mysqldump -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" | gzip > "$BACKUP_DIR/db-$(date +%Y%m%d-%H%M%S).sql.gz"
# Backup WordPress files
tar czf "$BACKUP_DIR/files-$(date +%Y%m%d-%H%M%S).tar.gz" -C /var/www wordpress
# Keep only 30 days of backups
find "$BACKUP_DIR" -type f -mtime +30 -delete
# Verify backups
ls -lah "$BACKUP_DIR" | tail -5
# Upload to cloud storage (S3, Backblaze, etc.)
# aws s3 sync "$BACKUP_DIR" s3://my-backup-bucket/wordpress/
echo "Backup completed: $(date)" >> /var/log/wordpress-backup.log
EOF
sudo chmod +x /usr/local/bin/wordpress-backup.sh
Schedule backups:
sudo crontab -e
# Daily backup at 2 AM
0 2 * * * /usr/local/bin/wordpress-backup.sh
Test backup restoration:
# Extract and verify backup
cd /tmp
tar xzf /backups/wordpress/files-20240101-020000.tar.gz
# Verify database backup
zcat /backups/wordpress/db-20240101-020000.sql.gz | head -20
# Restore full backup (test environment)
mysqladmin -u root -p create wordpress_test
mysql -u root -p wordpress_test < <(zcat /backups/wordpress/db-20240101-020000.sql.gz)
Conclusion
WordPress security requires continuous vigilance and multi-layered protections. This guide covers essential hardening practices including secure file permissions, protected configuration, strong authentication, WAF deployment, regular scanning, security headers, automatic updates, and reliable backups. Implementation should be systematic, starting with foundational security (permissions, config protection), then adding authentication hardening and monitoring. Regular security audits and vulnerability scanning ensure defenses remain effective against evolving threats. A properly secured WordPress installation provides confidence that your site, user data, and business are protected from common and advanced attacks.


