WordPress is the most popular CMS and the most attacked one. 40% of hacks happen through outdated plugins. This checklist closes 95% of threats.
1. Updates — top priority
- WordPress core — update immediately
- Plugins and themes — weekly
- Remove unused plugins and themes
- Enable security auto-updates: Dashboard → Updates → Enable auto-updates
2. Protecting wp-admin
# In .htaccess, restrict access to wp-admin by IP:
<Directory /var/www/site/wp-admin>
Order deny,allow
Deny from all
Allow from YOUR_IP
</Directory>
- Change the login URL: WPS Hide Login plugin
- Enable two-factor authentication: WP 2FA plugin
- Limit login attempts: Limit Login Attempts Reloaded
3. wp-config.php hardening
// Unique secret keys (generate at: https://api.wordpress.org/secret-key/1.1/salt/)
define('AUTH_KEY', 'unique_string');
// Disable file editing from the admin panel:
define('DISALLOW_FILE_EDIT', true);
// Limit revisions:
define('WP_POST_REVISIONS', 5);
// Disable external HTTP requests (if you do not need them):
// define('WP_HTTP_BLOCK_EXTERNAL', true);
4. Security plugin
Install one of the following:
- Wordfence — firewall + malware scanner + IP blocking
- Sucuri Security — file integrity monitoring
- iThemes Security — comprehensive protection
5. File permissions
find /var/www/wordpress -type f -exec chmod 644 {} \;
find /var/www/wordpress -type d -exec chmod 755 {} \;
chmod 600 wp-config.php
Common Errors and What They Mean
| Message | Cause | Fix |
|---|---|---|
| Too many login attempts, please try again later | A login-limiting plugin blocked your IP after several wrong passwords | Wait out the timeout (usually 15–60 minutes) or clear the block in the plugin settings |
| This site may be hacked (in Google Search Console) | Google found a malicious redirect, spam content, or injected script on the site | Scan the files, remove the foreign code, and request a re-review in Search Console |
| There has been a critical error on this website | A security plugin conflicts with the theme or another plugin, often right after an update | Enable WP_DEBUG, find the culprit in debug.log, and disable it via FTP or SSH |
| Invalid command 'RewriteEngine', perhaps misspelled | A rule from the security plugin landed in .htaccess, but Apache's mod_rewrite is disabled | Enable the module: a2enmod rewrite && systemctl restart apache2 |
| 403 Forbidden when visiting the old /wp-admin/ address | This is the expected behavior of the login-URL-change plugin, not an error | Use the new login address shown in the WPS Hide Login settings |
Comparing Security Plugins
| Plugin | Firewall | Malware scanner | Pro price |
|---|---|---|---|
| Wordfence | Yes, at the PHP level before WordPress loads | Yes, signature database updated daily | $119/year per site |
| Sucuri Security | Cloud WAF, a separate paid service | Yes, plus file integrity monitoring | $199/year per site |
| iThemes Security | Basic plugin-level rules | Limited, no signature database | $99/year for one site |
When the Checklist Is Not Enough
The checklist closes typical threats, but some situations need more:
- A vulnerability in custom-built theme or plugin code — this needs a source code audit, not a ready-made security plugin
- Infection through a neighboring site on the same shared hosting (cross-site contamination) — only account isolation or moving to a VDS helps here
- "Nulled" versions of paid plugins and themes from free forums — these often already have a backdoor built in before installation
- Compromise of the hosting account, FTP, or the domain registrar's panel — WordPress has nothing to do with it, yet the site still ends up hacked
If the site is already compromised, this checklist won't clean it up — diagnosis and cleanup come first, as described in "Hacked Website: Diagnosis, Cleanup, and Recovery". A broader server-level checklist for the box running WordPress is in "WordPress security: a complete site protection checklist".
Verifying the Result
After applying the checklist, confirm the protection actually works:
# Is file editing from the admin panel disabled
wp eval 'var_dump(defined("DISALLOW_FILE_EDIT") && DISALLOW_FILE_EDIT);'
# wp-config.php permissions should be 600 or 640, not 644
stat -c "%a %n" wp-config.php
# The old login address should return 404, not a login form
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-admin/
# Active security plugins via WP-CLI
wp plugin list --status=active | grep -i "wordfence\|sucuri\|ithemes"
Also put fail2ban in front of the server — it bans IPs based on Nginx/Apache logs before the request even reaches WordPress.