The plugin is the last layer
A security plugin is useful. It scans, it limits login attempts, it applies a firewall in PHP. It is also a plugin, which means it runs after the request has reached WordPress, it can be disabled by anyone with admin access, and it is one more piece of code with its own vulnerabilities. Most of what actually keeps a WordPress site secure happens before, around and outside it.
Here is the hardening we apply to every site, none of which requires a security plugin, in the order it earns its keep.
1. Updates, promptly, on a schedule
The overwhelming majority of WordPress compromises exploit a known vulnerability in a plugin or theme for which a fix had already been released. Not zero-days. Not clever attacks. A hole that was patched weeks earlier on a site that had not updated.
Monthly updates, tested on staging, with security releases applied within days rather than at the next cycle. Automatic minor core updates on. Plugins that have not been updated by their author in two years removed. This single practice does more than everything else on this list combined.
2. Fewer accounts, stronger accounts, two factors
- Every administrator account belongs to a named, current person. The freelancer from 2021 and the
adminuser with the guessable password are gone. - Editors edit, administrators administer. Most people who update content do not need the ability to install plugins.
- Two-factor authentication on every administrator account, without exception. Also on the hosting account and the domain registrar, which are the accounts that matter more than WordPress itself.
- Unique, long passwords, in a password manager. The single most common way in is a password reused from a breached service.
3. Disable file editing from the admin
One line in wp-config.php:
define( 'DISALLOW_FILE_EDIT', true );Removes the theme and plugin editors from the admin. An attacker who gets admin access can no longer paste a backdoor into a theme file from the browser. Code changes go through deployment, where they belong.
For sites where plugins are managed by deployment rather than the admin, also:
define( 'DISALLOW_FILE_MODS', true );which removes install and update from the admin entirely. Appropriate for sites under version control; not for sites the client updates themselves.
4. File permissions and ownership
Files 644, directories 755, wp-config.php 600 or 640, owned by the deploy user rather than the web server where the hosting model allows it. Nothing under wp-content/uploads should be executable, and the server should refuse to run PHP from the uploads directory:
# .htaccess in wp-content/uploads
<FilesMatch "\.php$">
Require all denied
</FilesMatch>Or the equivalent location block in nginx. Uploads is where injected shells land; making it a place where PHP cannot execute neutralises most of them.
5. Move or protect the configuration
wp-config.php can live one directory above the web root; WordPress looks there automatically. Where it cannot, deny web access to it at the server level. Rotate the authentication salts in it when a site is taken over or after any suspected compromise, which invalidates every existing session.
Set DISALLOW_UNFILTERED_HTML for multi-author sites so editors cannot embed scripts in content, and set FORCE_SSL_ADMIN.
6. Turn off what you do not use
- XML-RPC, unless something genuinely uses it. It is a brute-force amplifier. Block at the server.
- The REST API user enumeration endpoint (
/wp-json/wp/v2/users) for sites that do not need public author data. - Author archives, which expose usernames via
?author=1, unless the site uses them. - Directory listing.
- Pingbacks.
- The version string in the page head and RSS, which is minor but free.
7. Filter at the edge
A web application firewall in front of the site, at Cloudflare or the host, blocks known attack patterns, bad bots and brute-force traffic before it reaches PHP. Rate-limit wp-login.php and xmlrpc.php. Geo-block the admin if the business operates in one country. Challenge requests with no user-agent. This is what a security plugin's firewall does, moved to a layer that costs the server nothing and cannot be disabled from the WordPress admin.
8. Least privilege for the database
The database user WordPress connects with needs SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP on its own database and nothing else. Not GRANT, not access to other databases, not FILE. A SQL injection in a plugin then cannot read the neighbouring site's tables or write to the filesystem.
9. Backups, offsite, tested
Not a hardening measure in the strict sense, but the thing that turns a compromise from a disaster into an afternoon. Daily, stored somewhere other than the hosting account, retained for at least thirty days so a clean copy exists from before the compromise was noticed, and restored to staging once a quarter to prove they work.
10. Know what changed
File integrity monitoring at the host or via a lightweight cron script that hashes core, plugin and theme files and alerts on changes. Login alerts for administrator accounts. A log of admin actions. None of this prevents anything; all of it turns detection time from weeks into hours.
Then, the plugin
With the above in place, a security plugin adds malware scanning against a signature database and a convenient dashboard. That is worth having. But the site is secure because of the ten items above, and a plugin that is the only security measure is a lock on a door with no walls.
This list is the security section of every WordPress build we deliver and every onboarding onto a care plan. Most of it is an afternoon's work once, and the first item is the one that has to keep happening.