9 min read
Hardening Your WordPress Site: Measures That Actually Work
Most WordPress sites get compromised through one of four doors: a stolen or guessed admin password, an unpatched plugin, a writable file that should not be writable, or a hosting account that was never locked down. Hardening is the work of closing those doors one at a time, and it is mostly configuration rather than expensive tooling. This guide walks through what we do on every site we maintain, in the order we do it.
We are writing this against WordPress 7.0 (released May 2026) and PHP 8.2 and later. If you are still on a 6.x branch, everything here still applies, but update first. Running an old core version is the single worst thing you can do for security, and no amount of .htaccess rules makes up for it.
Start with accounts, because that is where most breaches begin

Credential attacks are boring and they work. Bots try leaked password lists against wp-login.php all day, every day, on every WordPress site that exists. The fix is not a clever login URL; it is making the password alone insufficient.
Turn on two-factor authentication for every privileged role
WordPress 7.0 still does not ship 2FA in core, so you need a plugin. The Two Factor plugin (maintained by WordPress core contributors) is free and does TOTP apps, FIDO security keys and backup codes. WP 2FA is the other one we reach for when a client wants to force enrolment with a grace period.
Make it mandatory for Administrator, Editor, Shop Manager and any custom role that can publish, install or edit code. Authenticator apps and hardware keys are better than SMS or email codes. If your team uses Google Workspace or Microsoft 365, single sign-on through an identity provider is cleaner still because offboarding happens in one place.
Audit who actually has an account
Go to Users and sort by role. On most sites older than two years we find former contractors, test accounts named “admin2”, and at least one person who left the company. Delete or demote them. Then check Users → Profile → Application Passwords for each remaining admin and revoke anything nobody can explain. Application passwords bypass 2FA by design, so a forgotten one is a permanent back door.
Rename the admin account and limit login attempts
If your primary account is literally called “admin”, create a new administrator with a different username, log in as it, and delete the old one (reassigning content to the new user). Then install a rate limiter. Limit Login Attempts Reloaded, or the login protection built into Wordfence or Solid Security, will lock an IP after a handful of failures. Five attempts and a 20 to 60 minute lockout is a reasonable default.
Lock down the wp-config file
A few constants in your wp-config file remove whole categories of attack. Add these above the line that reads “That’s all, stop editing!”:
// Disable the theme and plugin code editors in wp-admin.
define( 'DISALLOW_FILE_EDIT', true );
// Optional: block plugin/theme installs and updates from the dashboard.
// Only do this if you deploy via Git or WP-CLI.
define( 'DISALLOW_FILE_MODS', true );
// Force SSL on the admin and login screens.
define( 'FORCE_SSL_ADMIN', true );
// Hide PHP errors from visitors. Log them instead.
define( 'WP_DEBUG', false );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );
DISALLOW_FILE_EDIT is the one everyone should set. If an attacker gets an admin session, the built-in editor turns that into remote code execution in about ten seconds. DISALLOW_FILE_MODS is stronger but it also blocks one-click updates, so only use it on sites where updates run through a deployment pipeline.
While you are in the file, regenerate the salts. Visit api.wordpress.org/secret-key/1.1/salt/, paste the eight new lines over the old ones, and save. Every logged-in user will be signed out, which is what you want after a suspected compromise or a staff change.
Finally, change the table prefix if it is still “wp_”. That is a new-install job. On an existing site the risk of breaking something outweighs the small benefit, so we skip it and rely on the other layers.
Fix file permissions and ownership
The WordPress hardening documentation is clear on this: directories should be 755, files 644, and the wp-config file tighter still (600, or 640 if your host needs it). On a server you control, run the following from the WordPress root, then tighten wp-config by hand:
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
If 600 on the wp-config file breaks the site, your PHP process runs as a different user from the file owner. Use 640 and make the web server group the file’s group. Never use 777 on anything, including uploads. Managed hosts usually get this right; it is on VPS and shared cPanel accounts that we find writable core files.
Ownership matters as much as mode. The ideal setup has files owned by your deploy user, with the web server only able to write to wp-content/uploads (and wp-content/upgrade if you update through the dashboard). That way a vulnerable plugin cannot rewrite wp-includes.
Block direct access to things visitors never need
On Apache, these rules in the root .htaccess stop the most common probing. Nginx users translate them to location blocks.
# Stop PHP executing inside uploads
# (put this in wp-content/uploads/.htaccess)
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
# Disable XML-RPC if nothing uses it
<Files "xmlrpc.php">
Require all denied
</Files>
You will often see a Files directive that denies web access to the wp-config file. It works, but a cleaner option is to move the file one directory above the web root. WordPress looks for it there automatically, and a file outside the document root cannot be served by the web server under any configuration.
The uploads rule is the high-value one. A surprising number of plugin vulnerabilities boil down to “upload a file with a .php extension through a form that did not check”. If PHP cannot execute in that directory, the shell they dropped does nothing.
On XML-RPC: Jetpack and some mobile apps still use it. If you run Jetpack, do not block the file outright; instead leave it and let your security plugin rate-limit it. Everyone else can deny it.
Also turn off directory listing with Options -Indexes and, in Settings → General, make sure “Anyone can register” is unchecked unless you are deliberately running a community. If you are, restrict signups to the domains you expect; our free Restrict Email Domain plugin does exactly that and cuts spam registrations noticeably.
Keep the software current, and be picky about what you install

Plugins are the attack surface. Patchstack’s annual data puts well over 90 percent of disclosed WordPress vulnerabilities in plugins and themes rather than core. So the policy is simple: fewer plugins, from maintained sources, updated promptly.
A practical update routine
- Enable auto-updates for WordPress minor releases (on by default) and for plugins you trust. Go to Plugins, tick the ones you want, and choose “Enable Auto-updates” from the bulk menu.
- For anything that touches payments, memberships or custom code, update on staging first. Most managed hosts give you a one-click staging copy.
- Once a month, open Plugins and look for anything not updated in over a year, or flagged as “not tested with your version of WordPress”. Replace or remove it.
- Delete deactivated plugins and themes. Inactive code can still be exploited if the file is reachable by URL.
- Subscribe to a vulnerability feed. Wordfence, Patchstack and WPScan all publish free alerts, and Solid Security and Patchstack will auto-patch some issues before the vendor ships a fix.
Check your PHP version too. WordPress 7.0 raised the minimum, and hosts are dropping 8.0 and 8.1 from support. PHP 8.2 or 8.3 gets you security fixes and noticeably better performance.
Put a firewall and a backup in front of all of this

Hardening reduces the odds of compromise. A firewall and backups handle the day the odds catch up with you.
Firewall options compared
| Option | Where it runs | What it is good at | Trade-off |
|---|---|---|---|
| Cloudflare (free or Pro) | DNS edge, before your server | Blocking bots and floods before they use your CPU; managed WAF rules on Pro ($20/month) | Needs DNS moved; the free plan’s WAF is basic |
| Wordfence (free or Premium) | Inside WordPress | WordPress-specific rules, malware scan, login protection; free rules lag Premium by 30 days | Runs in PHP, so it adds some load |
| Solid Security / Patchstack | Inside WordPress | Virtual patching of known plugin vulnerabilities | Less effective against generic floods |
| Host-level WAF (Kinsta, WP Engine, Cloudways) | Server | Zero configuration; maintained by the host | You cannot tune it much |
Our usual stack is Cloudflare in front plus one WordPress-level scanner. Running two PHP firewalls at once gains nothing and occasionally breaks checkout.
Backups you have actually restored
Daily off-site backups with at least 14 days of retention, stored somewhere other than the web server. UpdraftPlus, BlogVault and host snapshots are all fine. What matters is that you have done a test restore to staging at least once, because a backup that has never been restored is a hope, not a plan. We cover the options in more depth in our backup plugins roundup.
Extra steps for community, membership and store sites
Sites with open registration (BuddyPress, BuddyBoss, WooCommerce, LMS platforms) carry more risk because untrusted users can log in and upload content. A few specifics:
- Restrict allowed upload types. Keep to images, PDFs and whatever your community needs. Do not enable SVG uploads for subscribers; SVG can carry scripts.
- Rate-limit registration and password resets at the Cloudflare level, not only in WordPress.
- Give every REST endpoint in custom code a proper
permission_callback. “__return_true” on an endpoint that writes data is the most common mistake we find in client audits. - On WooCommerce, enable the built-in card testing protection (WooCommerce → Settings → Payments → Fraud protection in WooPayments, or the equivalent in your gateway) and require account creation for digital products so refunds and chargebacks stay traceable.
- Review who has the
unfiltered_htmlcapability. On multisite only super admins have it; on single sites, Editors and Admins do. If editors do not need to paste raw scripts, remove it with a small mu-plugin.
Frequently asked questions
Does changing the login URL do anything?
A little. It stops the dumbest bots, but any tool that looks for the redirect from /wp-admin finds the new URL in one request. Treat it as a noise reduction measure, not a security control. 2FA and rate limiting are what stop logins.
Should I hide the WordPress version number?
You can remove the generator tag, but scanners fingerprint WordPress from file paths and readme.html anyway. Spend the time on updates instead. A current version has nothing to hide.
Is a security plugin enough on its own?
No. A scanner tells you after something happened. File permissions, 2FA, DISALLOW_FILE_EDIT and timely updates stop it happening. Use both.
What do I do if the site is already hacked?
Take it offline or put it in Cloudflare “Under Attack” mode, change every password (WordPress, hosting, database, FTP), restore from a clean backup that predates the compromise, then apply everything in this article before bringing it back. If you cannot find a clean backup, a fresh core install plus a scan of wp-content is the fallback.
Where to start
If you only have an hour, do these four things: turn on 2FA for all admins, add DISALLOW_FILE_EDIT to wp-config, drop the PHP-blocking .htaccess into uploads, and delete every plugin you are not using. That closes the doors most attackers walk through. Then schedule the rest over the next couple of weeks, and put a recurring monthly reminder in your calendar for the update review.
If you would rather hand this off, our WordPress security hardening service does the full checklist above plus a malware scan and a written report, and the monthly care plans keep the updates and backups running after that.
Related reading