WordPress Security Hardening in Practice: Complete Site Protection Config Guide
Hardens WordPress across wp-config.php, Nginx rules, login security, file permissions, and backups. Includes copy-paste configs and curl checks for xmlrpc.php, wp-admin, and upload PHP execution.
Harden WordPress with least privilege and defense in depth: lock down wp-config.php, add Nginx deny rules, restrict logins, minimize file perms, and automate backups.
Use cases
WordPress is the most widely used CMS and a frequent attack target. This guide hardens WordPress across five layers—config file, web server, login, file permissions, and backup/restore—with steps and code you can apply on self-hosted, corporate, and ecommerce sites.
Prerequisites
- WordPress 5.x/6.x site with server root or admin rights
- Web server: Nginx 1.18+ or Apache 2.4+ (Nginx examples below)
- Read/write access to wp-config.php and the site directory; back up the database and site files first
How it works
WordPress hardening follows least privilege and defense in depth, in four layers:
- Config: security keys, debug switches, table prefix, and file-edit rights in wp-config.php
- Web: Nginx/Apache rules to block xmlrpc.php, restrict wp-admin, hide version
- Login: cap login attempts, enforce strong passwords and 2FA
- System: minimize file perms, disable PHP in sensitive dirs, scheduled backups
Smaller attack surface, lower breach chance. That is the core logic.
Steps
Step 1: Harden wp-config.php
Disable file editing and debug output (add to wp-config.php) and change the table prefix:
define('DISALLOW_FILE_EDIT', true); // Disable in-dashboard online editing of theme/plugin files
define('WP_DEBUG', false); // Disable debug output in production
define('WP_DEBUG_DISPLAY', false);
$table_prefix = 'wp_9xK2'; // Change the database table prefix (ignore if already set at install)
Go to https://api.wordpress.org/secret-key/1.1/salt/ and generate new keys. Overwrite AUTH_KEY, SECURE_AUTH_KEY, and the other six constants so old login cookies are invalidated.
Step 2: Nginx protection rules
Add these rules to the site server block:
# Block XML-RPC (common entry for brute force and DDoS)
location = /xmlrpc.php { return 403; }
# Deny direct access to PHP files under wp-includes
location ~* ^/wp-includes/.*\\.php$ { deny all; }
# Deny PHP execution in uploads and cache directories
location ~* ^/wp-content/(?:cache|uploads)/.*\\.php$ { deny all; }
# Allow wp-admin only from specified IPs (replace with your admin IP)
location ^~ /wp-admin {
allow 203.0.113.10; # Admin entry IP, change as needed
deny all;
try_files $uri $uri/ /index.php?$args;
}
# Rate-limit admin logins: max 5 per IP per minute
location = /wp-login.php {
limit_req zone=login burst=5 nodelay;
try_files $uri /index.php?$args;
}
Matching Nginx rate-limit zone (inside the http block):
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
Run nginx -t && systemctl reload nginx to apply.
Step 3: Login security and 2FA
Install and enable Limit Login Attempts Reloaded (recommend: lock 30 minutes after 5 failed attempts). Enable WP 2FA or Google Authenticator on admin accounts. Delete the default admin username. Set a unique strong password for the admin (≥14 chars, mixed case, digits, specials).
Step 4: Minimize file permissions
chown -R root:www-data /var/www/your-site # Owner root, group is the web user
find /var/www/your-site -type d -exec chmod 755 {} \;
find /var/www/your-site -type f -exec chmod 644 {} \;
chmod 640 /var/www/your-site/wp-config.php # Config file readable only by root group
chown -R www-data:www-data /var/www/your-site/wp-content/uploads # Uploads directory needs write permission
Step 5: Scheduled backups and updates
# Daily database backup
mysqldump -u wpuser -p wpdatabase | gzip > /backup/wp_$(date +%F).sql.gz
# Daily incremental site file backup
rsync -a --delete /var/www/your-site/ /backup/site_$(date +%F)/
# Update policy: back up first, then upgrade core/plugins/themes on the dashboard Updates page
Verify the configuration
# 1. XML-RPC should return 403
curl -s -o /dev/null -w "%{http_code}\n" https://your-domain/xmlrpc.php
# 2. Unauthorized IP access to wp-admin should return 403
curl -s -o /dev/null -w "%{http_code}\n" https://your-domain/wp-admin/
# 3. Response headers should not expose version information
curl -sI https://your-domain/ | grep -i x-powered-by
# 4. PHP files in the uploads directory should be blocked
curl -s -o /dev/null -w "%{http_code}\n" https://your-domain/wp-content/uploads/test.php
Expected results: 403 / 403 / no output / 403. Then check core file integrity with WP-CLI: wp core verify-checksums. Optionally rescan with WPScan to confirm the fixes.
FAQ
Q1: After IP-restricting wp-admin, I cannot log in from a new network. What now?
SSH in, comment out the allow/deny rules or add your current IP, then reload Nginx. If SSH is unavailable, use the cloud vendor VNC console. Extract the allow block into a separate file (e.g. admin_ip.conf) so it is easier to maintain.
Q2: Will theme/plugin updates fail after disabling file editing?
No. DISALLOW_FILE_EDIT only blocks in-dashboard editing. Uploads, WP-CLI, and FTP updates still work. If you need in-dashboard edits (e.g. a child theme), set it back to true during a maintenance window.
Q3: After hardening I get 404s or static assets fail to load?
Usually a too-broad Nginx rule. The uploads PHP deny only matches *.php; images/CSS/JS are unaffected. If it still fails, run nginx -t and check error.log for the blocked path.
Summary
This guide hardens WordPress on five axes: config, web, login, permissions, and backups. Lock down wp-config.php and disable file editing, block XML-RPC and restrict wp-admin in Nginx, add plugin-level login protection, set 755/644 perms, and run daily backups. Hardening is not a one-shot. Scan monthly with WPScan and keep core/plugins updated so you have a harden–scan–rescan loop.