June 7, 2026
The WordPress Security Hardening Checklist We Use After Every Malware Removal
Cleaning malware off a WordPress site is only half the job. If you don't change anything about how the site was configured, the same hole that let the attacker in stays wide open — and you'll be back in the same spot in a few weeks. This is the checklist we work through after every cleanup, with honest notes about what you can handle yourself and what's worth handing to someone who does this for a living.
Why hardening matters more than the cleanup itself
When we work an incident, the malware removal is the urgent part. The hardening is the part that actually keeps you safe. We've seen sites where another contractor scrubbed visible spam, declared victory, and walked away — only for the customer to call us a month later because the spam is back. In one case we worked, a Southern California contractor's site had a self-healing backdoor that the previous cleanup completely missed. The attacker had planted code that would rewrite itself any time WordPress reloaded, plus 115+ hidden spam posts pointing to gambling sites. The fix wasn't just removing the malicious files; it was closing every door we could find so the same attacker couldn't waltz back in through the same hole.
That's what this checklist is for. Think of it less as a security audit and more as a post-incident lockdown. If you're reading this and your site hasn't been hit yet, even better — most of this prevents the first attack, not just the second one.
File and folder permissions: 755, 644, 600
This is the boring foundation and almost every hacked site we look at has it wrong. WordPress runs on a Linux server, and every file and folder has a permission code that tells the server who can read it, write to it, or execute it. Get this wrong and you've either left files writable that shouldn't be, or you've broken your own site trying to lock it down.
The defaults we set after every cleanup:
- Directories: 755 — owner can read, write, execute; everyone else can read and execute.
- Files: 644 — owner can read and write; everyone else can only read.
- wp-config.php: 600 — only the owner can read or write. Nobody else, not even other users on the server, should be able to look at this file. It contains your database password.
If you have SSH access, you can fix the whole site in two commands from your WordPress root directory:
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
chmod 600 wp-config.php
DIY verdict: Doable yourself if you're comfortable in a terminal or your host has a file manager that shows permissions. If you've never used SSH and the words "chmod" make your eyes glaze over, this is a 15-minute job for someone who has, and worth not guessing on.
One warning: some plugins (caching plugins especially) need write access to specific folders inside wp-content. After setting permissions globally, test the site. If something breaks, the fix is usually loosening a specific folder, not loosening everything.
wp-config.php hardening
wp-config.php is the file that controls how WordPress connects to your database and a lot of how it behaves. There are a handful of lines you should add or change after a cleanup.
Disable the in-dashboard file editor. WordPress lets administrators edit plugin and theme files directly from the dashboard. That's a feature attackers love — if they get one admin account, they get a code editor. Turn it off:
define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', true);
The second line goes further and blocks plugin/theme installs and updates from the dashboard too. That's stricter than most people want day-to-day; the first one alone is the minimum.
Rotate your salts. WordPress uses a set of secret keys called "salts" to encrypt login cookies. If an attacker ever had file access to your site, they may have copied your salts, which means they could potentially forge login sessions even after you've changed passwords. Rotate them.
Grab a fresh set from https://api.wordpress.org/secret-key/1.1/salt/ and paste them over the existing block in wp-config.php. Every active user gets logged out, which is exactly what you want.
Change the database table prefix (only on a new install or with a proper backup). The default WordPress table prefix is wp_. A lot of automated attacks assume that prefix. Changing it to something like wpz9k_ doesn't stop a determined attacker, but it breaks a lot of off-the-shelf SQL injection attempts.
This one is risky on a live site. You have to rename every table, update references inside two specific tables (usermeta and options), and change the prefix in wp-config.php. Miss a step and the site won't load. There are plugins that automate it, but I'd still take a full database backup first and test on a staging copy.
DIY verdict: Disabling file editing and rotating salts are safe to do yourself if you can edit wp-config.php over FTP or your host's file manager. Changing the database prefix on a live site is where I'd stop and call someone, or at least make sure you have a working backup you've actually tested restoring.
Plugin and theme audit
This is where most WordPress sites die. Every plugin is third-party code running on your server with significant access. Every theme is the same. The math is simple: more plugins = more attack surface, and abandoned plugins are time bombs.
After a cleanup, we go through the entire plugin and theme list and ask three questions:
-
Is it actively used? If you can't name what it does and where it shows up on the site, delete it. Deactivating isn't enough — the files are still on the server and still exploitable. Delete it.
-
Is it still maintained? Check the WordPress.org plugin page. If the last update was more than a year ago, or you see a banner saying "This plugin hasn't been tested with the latest 3 major releases," start looking for a replacement. Abandoned plugins are the single most common path we see attackers take into a small business site.
-
Is it from a source you trust? Nulled (pirated) premium plugins almost always come with backdoors built in. If you inherited a site and have no idea where a plugin came from, treat it as suspect.
Themes follow the same rules with one addition: delete every theme you're not using except the latest default WordPress theme (Twenty Twenty-Four or whatever's current). Keep that one as an emergency fallback if your active theme breaks.
Pin versions where you can, but don't let pinning become an excuse to skip updates. The right pattern is: test updates on staging, then roll them to production within a week or two. Sitting on a known-vulnerable plugin version for six months because "we don't want to break anything" is how sites get hit.
DIY verdict: This is yours. You know your site better than anyone. Nobody else can tell you which contact form plugin you actually use.
Login security: lockouts, 2FA, and the wp-admin path
The WordPress login page is the single most-attacked URL on the internet. Brute-force attempts against /wp-login.php run nonstop, from thousands of IP addresses, on every WordPress site that's online. There are three things to put in place.
Login lockout. After a small number of failed attempts (5 is reasonable), block that IP for a while. Plugins like Limit Login Attempts Reloaded or Wordfence's login security module do this in a few clicks. This alone eliminates the vast majority of brute-force noise.
Two-factor authentication for every admin account. Not optional, not "we'll get to it." Every account with admin or editor permissions needs 2FA. Use an authenticator app (Google Authenticator, Authy, 1Password's built-in TOTP). SMS is better than nothing but vulnerable to SIM-swap attacks. The Wordfence Login Security plugin handles this well and is free.
Hide or rename the login URL. Changing /wp-login.php to something like /secure-login-7k2 won't stop a targeted attacker who's specifically after your site, but it eliminates a huge volume of automated traffic. Plugins like WPS Hide Login do this without breaking anything. Write the new URL down somewhere — locking yourself out is embarrassing.
Get rid of the "admin" username if it exists. Default usernames are the first guess in every brute-force attempt. Create a new admin account with a non-obvious username, log in as that user, and delete the old admin account (assigning its posts to the new user).
DIY verdict: All of this is DIY. The plugins are well-documented and the dashboard handles everything. Budget an hour.
XML-RPC and the REST API
This is where the answer gets nuanced. Both XML-RPC and the WordPress REST API are legitimate features that some plugins and integrations rely on, and both are also frequent attack targets.
XML-RPC (/xmlrpc.php) is an older protocol WordPress uses for things like the Jetpack plugin, the WordPress mobile app, and pingbacks. It's also been abused for brute-force amplification attacks (one request can attempt hundreds of passwords) and DDoS attacks against other sites.
If you don't use Jetpack, the WordPress mobile app, or any service that explicitly needs XML-RPC, turn it off. The cleanest way is at the web server level. In .htaccess for Apache:
<Files xmlrpc.php>
Order Deny,Allow
Deny from all
</Files>
For Nginx, in your server block:
location = /xmlrpc.php {
deny all;
}
If you do need XML-RPC for one specific service, restrict it to just that service's IP range instead of opening it to the world.
The REST API is trickier. WordPress itself uses it internally now, so you can't just turn it off without breaking things, including parts of the block editor. What you can do is restrict who can access certain endpoints — specifically the user enumeration endpoint, which lets anyone list your usernames by hitting /wp-json/wp/v2/users. A well-configured security plugin will block unauthenticated access to that endpoint by default.
DIY verdict: XML-RPC: DIY if you're confident you don't need it, and you can test by trying to use whatever services you rely on after disabling it. REST API restrictions: this is where I'd lean on a security plugin's preset rather than trying to write your own. Wordfence and similar tools handle the common cases well.
WAF rules, fail2ban, and log shipping (pro territory)
Here's where the checklist crosses out of DIY land for most small business owners.
A web application firewall (WAF) sits in front of your site and inspects traffic before it reaches WordPress. It blocks known attack patterns — SQL injection attempts, suspicious file uploads, requests targeting known plugin vulnerabilities. Cloudflare's WAF (paid tiers) and Sucuri are the common options for hosted WAFs. Wordfence runs a WAF at the WordPress application level, which is easier to set up but doesn't protect against attacks that bypass PHP entirely.
The reason this is pro territory isn't that the tools are hard to install — they're not. It's that a WAF tuned wrong either blocks legitimate users (login forms breaking, checkout flows failing) or lets through everything that matters. Getting the rules right means understanding your traffic patterns and tuning over time.
fail2ban is a server-level tool that watches log files and bans IPs that show abusive patterns — repeated 404s on sensitive paths, brute-force attempts that slip past WordPress's own lockout, scrapers hammering the site. It requires SSH access and root-level configuration. Powerful, but not a "weekend project" tool.
Log shipping and monitoring is the part nobody likes but matters most. If your site gets compromised, the difference between "we caught it in two hours" and "we caught it in two months" is whether anyone is actually looking at the logs. That means shipping web server logs, WordPress audit logs (a plugin like WP Activity Log handles this), and ideally PHP error logs to somewhere they can be queried — a service like Papertrail, Datadog, or a self-hosted Loki setup.
For a small business, the realistic version of this is a security retainer where someone else does the watching. You don't need an in-house SOC. You need someone with eyes on the logs who'll call you when something weird shows up.
DIY verdict: WAF basics (turning on Cloudflare's free tier, installing Wordfence) are DIY. Tuning, custom rules, fail2ban, and ongoing log review are where most owners are better off paying someone or accepting the risk consciously.
Putting it together: the priority order
If you're reading this and feeling overwhelmed, here's what I'd do in order if I had a Saturday afternoon and a recently-cleaned site:
- Fix file permissions (755/644/600).
- Add
DISALLOW_FILE_EDITand rotate salts inwp-config.php. - Delete every plugin and theme you don't actively use.
- Install a login security plugin, turn on 2FA for every admin account, enable login lockout.
- Block XML-RPC if you don't need it.
- Turn on Cloudflare (the free tier is fine to start) and point your DNS through it.
- Install an activity log plugin so you have a record if something goes wrong.
That covers maybe 80% of the realistic attack surface for a small business site and costs you an afternoon plus zero to fifteen dollars a month in plugins.
The remaining 20% — the WAF tuning, the log monitoring, the periodic review of new vulnerabilities affecting your specific plugins — is the part that doesn't fit in a Saturday afternoon. It's ongoing. That's what a security retainer is for, and it's the difference between a site that's hardened once and forgotten and a site that stays hardened as the threat landscape shifts under it.
When to call someone
Some honest signals that the DIY path isn't the right one:
- You've been hit before and you're not 100% sure the last cleanup got everything.
- You handle customer payment information, healthcare data, or anything else with regulatory weight.
- Your site is the primary way customers find or contact your business, and a day of downtime would actually hurt.
- You don't have time to keep up with plugin updates and you know it.
None of these mean you can't run a secure WordPress site. They just mean the cost of getting it wrong is higher than the cost of paying someone to get it right.
If you're somewhere on that spectrum and want a hand — whether that's a one-time hardening pass after a cleanup, or an ongoing retainer where someone else watches the logs — that's what the site hardening service is built for. Same checklist, applied properly, with the parts that need a pro actually getting done. You can also see what a full cleanup-and-hardening engagement looks like in the case study from the contractor site I mentioned earlier.
Either way: don't skip the hardening. Removing malware without closing the door is just buying yourself time until the next call.