This is the WordPress security checklist I work through when I harden a client site, and it is shaped by the sites I have had to clean up after the fact. Almost every hacked WordPress site I see got in through one of three doors: an outdated or abandoned plugin, a reused or weak admin password, or a nulled (pirated) theme. The checklist below closes those first, then adds the layers that limit damage and help you recover. You do not need a dozen security plugins to secure WordPress; you need the basics done properly and checked regularly. It is the same list I apply as part of my WordPress security hardening service.
1. Updates and plugin hygiene
This section matters more than everything else combined.
- Keep core updated. Minor core releases (security fixes) auto-install by default. Major versions do not unless you opt in. I apply majors within a week or two, after checking them on staging.
- Turn on auto-updates for plugins you trust. WordPress has had per-plugin auto-update toggles since 5.5. For small brochure sites I enable them for everything; on stores I enable them for security-focused and well-maintained plugins and handle the rest manually.
- Delete, do not just deactivate. A deactivated plugin's files are still on the server, and a vulnerable file can often be reached directly by URL.
- Remove abandoned plugins. If a plugin has not been updated in over a year, or was closed on wordpress.org, replace it. Closed plugins frequently mean an unpatched security issue.
- No nulled themes or plugins, ever. In my experience they are one of the most common sources of backdoors, and the "free" copy often costs a full cleanup.
WP-CLI makes auditing quick:
wp core version
wp plugin list --update=available
wp plugin list --status=inactive
wp theme list2. Logins, 2FA and least-privilege roles
- Unique, long passwords for every admin. Use a password manager. Credential stuffing from other breaches is far more common than anyone guessing a password.
- Two-factor authentication for every account with edit rights. The official Two Factor plugin on wordpress.org supports TOTP apps and security keys. Enforce it, do not just offer it.
- Least privilege. Content writers are Authors or Editors, not Administrators. Shop managers use the WooCommerce Shop Manager role. Agencies and freelancers get their own account, removed when the work ends.
- No "admin" username. It is the first name every brute-force bot tries.
- Limit login attempts at the plugin, WAF or server level (fail2ban works well if you control the server).
Audit administrators regularly. Attackers love adding a quiet extra admin:
wp user list --role=administrator --fields=ID,user_login,user_email,user_registeredIf you ever find an admin you did not create, treat the site as compromised and read what to do when your WordPress site is hacked before changing anything else.
3. WordPress hardening in wp-config.php
A few constants remove whole categories of attack. Add them above the "stop editing" line:
// Remove the theme and plugin file editor from wp-admin
define( 'DISALLOW_FILE_EDIT', true );
// Force HTTPS for logins and the dashboard
define( 'FORCE_SSL_ADMIN', true );
// Optional: block plugin/theme installs and updates from wp-admin entirely
// define( 'DISALLOW_FILE_MODS', true );DISALLOW_FILE_EDIT is the one I set everywhere. If someone steals an admin session, the built-in editor lets them paste PHP straight into a theme file. DISALLOW_FILE_MODS goes further and also disables installing and updating plugins from the dashboard, which only makes sense if updates happen through deployments or WP-CLI.
Also rotate the security keys and salts after any incident, or if they are the defaults from an old install. This logs everyone out:
wp config shuffle-salts4. Disable XML-RPC and review exposed endpoints
xmlrpc.php is a legacy remote API that most sites no longer need, and its system.multicall method lets bots try many passwords in a single request. Unless you rely on it (the Jetpack plugin and some older mobile or publishing tools do), block it at the web server rather than with a plugin, so PHP never runs.
Nginx:
location = /xmlrpc.php {
deny all;
}Apache (.htaccess):
<Files xmlrpc.php>
Require all denied
</Files>While you are there, block PHP execution in wp-content/uploads/. Nothing legitimate runs PHP from the uploads folder, and it is the favorite place to drop a web shell:
location ~* /wp-content/uploads/.*\.php$ {
deny all;
}Nginx uses the first regex location that matches, so put this block above your main location ~ \.php$ block or it will never apply. I leave the REST API on, because the block editor and many plugins depend on it, but I check that user enumeration via /wp-json/wp/v2/users does not reveal anything you would rather keep private.
5. File permissions and ownership
The standard, documented baseline:
| Path | Permission | Notes |
|---|---|---|
| Directories | 755 | Owner writes, everyone else reads and traverses |
| Files | 644 | Owner writes, everyone else reads |
wp-config.php | 600 or 640 | Only the owner (and web server group) can read it |
.htaccess | 644 | 444 if nothing needs to rewrite it |
Never 777. If a plugin tells you to chmod something to 777, the ownership is wrong, not the permission. Fix it in one go from the site root:
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
chmod 640 wp-config.phpOn a server you manage, run PHP-FPM as a dedicated user per site so one compromised site cannot read another's files. That is part of wider server work: my Linux server hardening checklist covers SSH, firewalls and users, and the server security hardening service is where I do it for you.
6. Put a WAF in front of the site
A web application firewall blocks known exploit patterns and bad bots before they hit PHP. You have two broad options:
- Edge WAF such as Cloudflare. Filters traffic before it reaches your server, adds rate limiting and bot controls, and takes load off the origin. I like protecting
/wp-login.phpwith a rate-limit or challenge rule here. If you go this route, lock the origin to only accept traffic from the proxy, otherwise attackers just bypass it. See my Cloudflare setup service. - Plugin WAF such as Wordfence. Runs inside WordPress, understands WordPress context, but uses your server's resources and only acts once the request reaches PHP.
Pick one approach as the main layer. Stacking three security plugins mostly adds slowness and conflicts.
7. Backups stored off the server
A backup on the same server as the site is not a backup: if the server is compromised, encrypted or deleted, so is the backup. My rules:
- Daily database backups and at least weekly file backups; hourly database backups for busy stores.
- Stored off-server: S3, Backblaze B2, Google Drive, or any storage the web server cannot delete from. Write-only credentials or object versioning are ideal.
- At least 30 days of retention, because infections often sit unnoticed for weeks.
- A tested restore. I restore to a staging site a few times a year. An untested backup is a hope, not a plan.
8. Monitoring and integrity checks
Hardening lowers the odds; monitoring shortens the time between an incident and you finding out.
- Uptime monitoring with alerts to a real person.
- File integrity checks. WP-CLI can compare core and plugin files against the official checksums from wordpress.org:
wp core verify-checksumsandwp plugin verify-checksums --all. Run them from cron and alert on any failure. - Login and user-change alerts, so a new administrator never goes unnoticed.
- Google Search Console, which emails you if Google detects malware or hacked spam content.
- Vulnerability alerts for your installed plugins, from your security plugin or a vulnerability database service.
The WordPress security checklist at a glance
- Core, plugins and themes up to date; auto-updates on where safe.
- Unused, abandoned and nulled plugins and themes deleted.
- Unique passwords and enforced 2FA for every editor and admin.
- Least-privilege roles; no shared or leftover admin accounts.
- No "admin" username; login attempts rate-limited.
DISALLOW_FILE_EDITset; HTTPS forced for admin.- Salts rotated after any incident.
- XML-RPC blocked unless needed.
- PHP execution blocked in uploads.
- Permissions 755/644,
wp-config.php600 or 640, never 777. - WAF in front, with the origin locked down.
- Off-server backups with 30+ days of retention.
- Restores tested.
- Uptime, integrity and user-change monitoring with alerts.
If you are comfortable with SSH and WP-CLI, you can work through this in an afternoon. If not, or if the site handles orders, memberships or anything you cannot afford to lose, it is reasonable to hand it over. My WordPress security service covers the full checklist plus ongoing updates and monitoring, and if you suspect the site is already compromised, start with WordPress malware removal instead, since hardening an infected site just locks the attacker in. Small hardening jobs are $20/hour; larger or ongoing work gets a fixed price agreed in writing.
Frequently asked questions
Do I need a security plugin to secure WordPress?
Not necessarily. Most of the protection comes from updates, 2FA, removing bad plugins, correct permissions and off-server backups. A security plugin or an edge WAF adds useful firewalling and alerts, but one well-configured tool is enough; stacking several causes slowdowns and conflicts.
Is it worth changing the wp-admin login URL?
It cuts down bot noise in your logs, but it is obscurity, not security. A strong password, 2FA and rate limiting protect the login properly. If you do hide it, treat it as a small extra, not a replacement for those.
How often should I review WordPress security?
Updates weekly, or automatically. A quick audit of admin users, inactive plugins and backup status monthly. A full review, including a test restore, a few times a year and after any major change such as a redesign, host move or new plugin set.
Is shared hosting less secure for WordPress?
It can be. On poorly isolated shared hosting, one compromised account can sometimes affect others. Good hosts isolate accounts per user. Either way, most WordPress hacks come from the site itself, such as outdated plugins and weak passwords, rather than the hosting type.