Many WordPress compromises I've been asked to clean up didn't start with a sophisticated exploit. They started with a nulled plugin, a login form with no rate limiting, or a database user with more privileges than the site needed. WordPress core has a dedicated security team and regular security releases, so the weak point is usually configuration, plugins, and the gap between "the site works" and "the site is actually hardened."
Here's the checklist I run against client sites, roughly in order of how much damage each gap can cause. Where a step is covered by WordPress's own documentation, I've linked it.
Attack surface starts with what you've installed, not what WordPress ships with
Every plugin and theme you activate runs with the same privileges as WordPress core. A plugin with an unauthenticated file-upload vulnerability can give an attacker the same access as a flaw in core, and third-party code generally gets less review than core does.
- Audit what's actually active, and delete what you don't use. A deactivated plugin can't run, but it still sits on the server and can be reactivated later, so an outdated one is still a liability. Remove anything you're not using. The managing plugins guide covers how.
- Keep plugins and themes updated, and check changelogs for security fixes. WordPress enables automatic updates for minor and security core releases by default, but plugin and theme auto-updates are opt-in, so someone has to turn them on or check for updates regularly. Running a plugin with a published, patched vulnerability is a common cause of compromise. See the automatic updates documentation.
- Never use a nulled or "premium for free" plugin or theme. Pirated copies are a common way backdoors get distributed, and I've found PHP mailers and base64-encoded web shells hidden inside "free" copies of commercial plugins.
// a pattern worth searching for in wp-content: obfuscated eval calls
// some legitimate code uses eval or base64, so treat each match as something to review, not proof of a compromise
eval(base64_decode($_REQUEST['data']));
Authentication is where automated attacks land
A large share of the login traffic public WordPress sites receive is automated: bots trying credential lists and brute-force guesses against wp-login.php and xmlrpc.php. You don't need to stop a determined targeted attacker to block most of it. You need to stop automation.
- Rate-limit or block repeated failed logins. A handful of failed attempts from the same IP in a short window should trigger a temporary lockout. This blocks most automated guessing, though it doesn't replace strong passwords.
- Disable XML-RPC if you don't use the mobile app or remote publishing.
xmlrpc.phpis enabled by default in WordPress. Itssystem.multicallmethod can bundle many login attempts into one HTTP request, which makes it an efficient target for password guessing. WordPress'sxmlrpc_enabledfilter turns off the authenticated XML-RPC methods. Note that the filter doesn't remove the endpoint entirely, so blocking the file at the web server is the simplest complete option. - Use strong, unique passwords and two-factor authentication for every account that can publish. An editor account can publish and edit other people's posts, so a compromised editor is a serious incident even without admin rights. Editors can't install plugins or create users by default, which limits the damage, but it's still worth protecting them properly. See the roles and capabilities reference.
- Don't use "admin" or other guessable usernames. WordPress's hardening guide advises against easily guessed names. Removing the username from the attacker's guesses means they also have to guess the password.
# block XML-RPC at the web server if you don't need it
location = /xmlrpc.php {
deny all;
}
Where this goes wrong in production
Security plugins get installed and then never configured. A firewall plugin set to log-only mode, or a two-factor plugin that isn't enforced for existing users, gives the appearance of security without the protection. During an audit I check enforcement, not just presence.
File permissions get loosened to fix a symptom and never tightened back up. chmod -R 777 makes an upload error go away and leaves every file writable by any process that can reach it, including a compromised plugin. wp-config.php should be one of the most restrictive files on the server, since it holds your database credentials and secret keys in plain text. WordPress's hardening guide recommends a 400 or 440 permission for it, and moving it above the web root where your host allows that.
The database user has more privilege than it needs. For normal operation WordPress needs SELECT, INSERT, UPDATE and DELETE on its own database. Upgrades and plugins that change the schema also need privileges like CREATE, ALTER, INDEX and DROP, and the hardening guide warns that revoking these can cause problems during updates. So the practical goal is a user with privileges on its own database only, with no global privileges and no access to other databases on the same server. A single-site compromise shouldn't become a multi-site one.
Backups exist but have never been tested. A backup you haven't restored is a hypothesis, not a safety net. I've seen automated backups fail silently for months after a plugin update changed a file path, and nobody noticed until the site was already compromised and the "backup" turned out to be an empty zip file.
Trade-offs and when NOT to go further
Hardening has a real UX and maintenance cost, and it's worth being honest about where it stops paying for itself.
Locking down the REST API can break things. The REST API powers the block editor and a lot of plugin functionality. Disabling it wholesale, rather than restricting specific unauthenticated endpoints, is a common mistake that breaks the admin dashboard without stopping an attacker who has other ways in.
Renaming wp-login.php or hiding the admin URL is obscurity, not security. It cuts down noise from the laziest bots, which has some value for server load, but it isn't a security control. Anyone determined to find your login page usually can, through the sitemap, redirects, or author archive pages.
Managed WordPress hosting with a built-in web application firewall can be cheaper than DIY hardening once you count your own time. For a single brochure site, a few hours of manual hardening is proportionate. For anything handling payments, customer data, or meaningful traffic, managed hosting with a WAF, automatic patching and malware scanning may be the better trade-off.
Why it matters for the business behind the site
A compromised WordPress site isn't just a technical inconvenience. It can get flagged by Google Search and by browser safe-browsing warnings, and in the worst cases it's used to attack the site's own visitors through injected scripts. Cleaning up a hacked site, including the reputational damage and the time a flagged site spends out of search, usually costs more than the hardening that would have prevented it. I treat security configuration as part of a standard WordPress build for that reason.
If your WordPress site has never had a proper security review, or you're not sure your backups actually restore, that's the kind of review I do before it becomes an incident. Get in touch and I'll tell you where the real gaps are.