Security
How to Clean a Hacked WordPress Site: The Exact Order That Works
How to clean a hacked WordPress site comes down to one thing most guides skip: the order. Contain the damage, clean the files and database, sweep the whole hosting account, reset users and keys, and only then deal with Google. Do those five steps out of order and you'll likely be doing them twice.
I've cleaned and hardened hacked WordPress sites on cPanel, Plesk and Cloudflare setups, and the ones that "came back" usually had a step done too early or skipped entirely. A scanner turned green, someone requested a Google review, and a forgotten backdoor quietly rebuilt the spam a week later.
This guide is the do-it-yourself version, with the real commands. It assumes you can log in to your hosting control panel and, ideally, use SSH (a secure command-line connection to your server) with WP-CLI (WordPress's command-line tool). If you'd rather hand the whole thing over, you can describe what you're seeing in the hacked-site questionnaire and I'll reply with a plan and a fixed quote within one business day. No pressure either way.
How to clean a hacked WordPress site: the order and why it matters
Each step protects the next one. Here's the sequence at a glance:
| Step | What you do | What goes wrong if you skip or rush it |
|---|---|---|
| 1. Contain | Take the site offline, snapshot it, change the hosting login | Visitors keep getting infected, and you destroy the evidence |
| 2. Files and database | Replace core and plugins, hunt backdoors, search the database | Spam scripts survive inside posts, options or uploads |
| 3. Hosting account | Check every site, cron job, FTP user and SSH key in the account | A neighbor install reinfects the site you just cleaned |
| 4. Users and keys | Remove rogue admins, rotate every password and the salts | The attacker logs back in with a key you never changed |
| 5. Google | Spam URLs return 410, rogue Search Console owners go, then the review | Google keeps the warning, or you request a review too early |
Notice that passwords sit at step 4, not step 1. That surprises people. If a backdoor is still running when you change your passwords, it can read the new ones straight out of wp-config.php. So you lock the front door early (the hosting login), clean house, and only then change every other key.
Step 1: Contain the damage before you clean anything
The urge is to start deleting files. Resist it for 20 minutes. Containment means three things: stop the harm to visitors, keep a copy of the crime scene, and lock the one door that controls everything.
Take the site offline (properly)
Google's own quarantine guidance says to take the site offline and calls the 503 status code (a "temporarily unavailable, come back later" signal) a useful way to tell search engines the outage is temporary. A 503 for a day or two won't hurt your rankings the way serving malware will.
Ideally the "back soon" page is served from outside the hacked server, for example by your host or a CDN rule. If that's not an option, this rule lets only your own IP address through:
# Temporary lockout: every visitor except your IP gets a 503 "back soon" page.
# Put it at the very top of the root .htaccess and remove it when you're done.
# Could break: behind Cloudflare, REMOTE_ADDR is Cloudflare's IP. Test the condition
# against %{HTTP:CF-Connecting-IP} instead. The Retry-After line needs mod_headers.
RewriteEngine On
RewriteCond %{REMOTE_ADDR} !^203\.0\.113\.25$
RewriteCond %{REQUEST_URI} !^/maintenance\.html$
RewriteRule ^ - [R=503,L]
ErrorDocument 503 /maintenance.html
Header always set Retry-After "7200"Swap 203.0.113.25 for your own public IP (search "what is my IP" to find it).
Snapshot the infected site
Before you change a single file, copy everything, malware included. You'll want it to compare against later, and to work out how they got in.
# Read-only for the live site: archives the files and exports the database
# into your home folder, outside the public web root.
cd ~ && tar -czf hacked-files-$(date +%F).tar.gz public_html
wp db export ~/hacked-db-$(date +%F).sql --path=public_htmlDownload both files to your computer, then delete them from the server once the cleanup is done.
Change the hosting login and call your host
Change your hosting control panel password now, from a computer you trust. Google's vulnerability checklist suggests scanning every computer used to sign in to the site, because an infected laptop will hand the new password straight back.
Then tell your host. They often see things you can't (server logs, other flagged files), and some will suspend the account if they find out on their own.
Step 2: Clean the WordPress files and database
This is the step everyone knows about. The trick is to replace files instead of editing them, and to treat the database as a second hiding place.
Replace core and plugins instead of patching them
First, find out what's been changed. WP-CLI compares each file against the official copies on WordPress.org:
# Read-only. Lists core files that don't match WordPress.org, plus unexpected files in the root.
wp core verify-checksums --include-root
# Same check for plugins from the wordpress.org directory. Premium plugins can't be checked this way.
wp plugin verify-checksums --allWhatever the result, I'd replace rather than repair. These commands swap core and free plugins for fresh copies and leave your content and settings alone:
# Re-download WordPress core over the top. Keeps wp-content and wp-config.php.
wp core download --force --skip-content --version=$(wp core version)
# Reinstall every plugin from wordpress.org. Premium plugins will fail here:
# download those fresh from the vendor's site and replace them by hand.
wp plugin install $(wp plugin list --field=name) --forceDo the same with your theme. If it's a premium or custom theme, compare it against a clean copy from the vendor or your developer. Delete any plugin or theme you don't use. Inactive code can still be called directly from the web.
Hunt for backdoors
A backdoor (a hidden way back in, such as a script that accepts commands or re-downloads the malware) is what turns one hack into three. These are the places I check every time:
# 1. PHP has no business in uploads. Every result is a suspect.
find wp-content/uploads -type f \( -name "*.php" -o -name "*.phtml" -o -name "*.phar" \)
# 2. Must-use plugins load automatically and can't be switched off in the dashboard.
# You should recognize every file in this folder.
ls -la wp-content/mu-plugins/
# 3. Common obfuscation patterns. A match is a lead to read, not proof of guilt.
grep -rlE --include="*.php" "eval *\( *(base64_decode|gzinflate|str_rot13)" .
# 4. Config files that tell PHP to load an extra file before WordPress even starts.
grep -rl "auto_prepend_file" --include=".user.ini" --include="php.ini" --include=".htaccess" .Don't trust file dates. Attackers routinely backdate their files so a "recently modified" search walks right past them. Also read every .htaccess file in the site, not just the one in the root. A single injected rewrite rule can send mobile visitors from Google to a spam site while your desktop looks fine.
If an uploads folder did contain PHP, block it from running there. On Nginx, the server block rule looks like this:
# Refuse to run any PHP-type file inside uploads. Could break: a badly built plugin
# that executes PHP from uploads (rare). On Apache, use a FilesMatch block with
# "Require all denied" in wp-content/uploads/.htaccess instead.
location ~* ^/wp-content/uploads/.*\.(php|phtml|phar)$ {
deny all;
}Search the database
Plenty of spam lives in posts, widgets and the options table, where no file scanner looks. wp db search checks every text column:
# Read-only searches. Expect some harmless matches (analytics snippets, embeds).
wp db search '<script' --all-tables --stats
wp db search 'eval(' --all-tables
# These two should be your own domain. A spam domain here means a redirect hack.
wp option get siteurl
wp option get homeThen list scheduled tasks inside WordPress. wp cron event list shows each hook and when it next runs. Anything with a random-looking name that you can't trace to a plugin you installed deserves a closer look.
Step 3: Sweep the whole hosting account
Here's where a lot of DIY cleanups fall over. On most shared hosting, every site in one account runs as the same server user. A forgotten test install in a subfolder, an old staging copy, or a parked domain from years ago can rewrite your main site at will.
Malware is a bit like a houseguest who made copies of your keys and left one under every flowerpot. You have to check all the flowerpots.
# Every WordPress install in this hosting account, with its version number.
find ~ -path "*wp-includes/version.php" -exec grep -H "wp_version =" {} \;
# Server-level scheduled tasks. Attackers add jobs that re-download malware.
crontab -l
# SSH keys allowed into this account. Every line should belong to someone you know.
cat ~/.ssh/authorized_keysWork through what you find:
- Old or abandoned installs: back them up off the server, then delete them. If you need one, clean it with the same steps as the main site.
- Cron jobs (tasks the server runs on a timer): remove any you didn't create, especially lines with
curl,wgetorphpfetching a remote URL. - FTP accounts: in cPanel or Plesk, delete any you don't use and change the password on the rest.
- SSH keys: remove keys you can't identify.
I go deeper into this in my piece on why WordPress malware keeps coming back, because the hosting account is where most reinfections hide.
Step 4: Reset users, passwords and keys
Now the house is clean, so new keys will stay secret. This is the step where the attacker finally loses access.
Find every administrator, including hidden ones
Some malware hides its admin account from the Users screen with a small must-use plugin. Asking the database directly gets around that, because this command runs plain SQL instead of going through WordPress's user screens:
# Lists every administrator straight from the database.
# Replace wp_ with your table prefix from wp-config.php (it appears twice).
wp db query "SELECT u.ID, u.user_login, u.user_email, u.user_registered FROM wp_users u JOIN wp_usermeta m ON m.user_id = u.ID WHERE m.meta_key = 'wp_capabilities' AND m.meta_value LIKE '%administrator%';"Delete any admin you don't recognize. wp user delete 1234 --reassign=1 removes user 1234 and hands their posts to user 1, so nothing gets lost.
Rotate every password, in this order
- Database password. Change it in your hosting panel, then update
DB_PASSWORDinwp-config.phpto match, or the site goes down. - SFTP and FTP passwords for every remaining account.
- The hosting panel password, one more time.
- Every WordPress administrator and editor password.
- Email accounts on the domain, if they share the same hosting.
Then refresh the salts (the secret keys that sign everyone's login cookies). The WordPress docs say you can change the security keys at any time to invalidate all existing cookies, which means everyone has to log in again. That's exactly what you want, because it kicks out any session the attacker stole. wp config shuffle-salts does it in one line.
Close the hole they used
Update WordPress, every plugin and every theme. Turn on two-factor login (a code from your phone on top of the password) for every admin account. Add define( 'DISALLOW_FILE_EDIT', true ); to wp-config.php so a stolen admin login can't edit plugin code from the dashboard.
If you can, work out how they got in. An outdated plugin with a known security hole is the usual answer. A reused password is the second. If you don't fix the entry point, you've cleaned up for the next visitor.
Step 5: Then fix Google
Only now does Google come into it. Request a review earlier and you risk failing it, and Google's own review guidance warns that requesting before the problem is fixed will only prolong the period your site is flagged.
See what Google sees
Your browser may show a normal site while Google sees spam. That's called cloaking (showing one thing to search engines and another to people). Three checks:
- Search
site:yourdomain.comin Google, then add words likeviagra,casinoorjapanto the search. Spam pages you never wrote will show up. - In Google Search Console (Google's free dashboard for site owners), open URL Inspection on a suspicious URL and run a live test. Look at the HTML Google fetched, not the page in your browser.
- Check Security issues in Search Console for the official verdict and sample URLs.
If the spam is in Japanese, my guide to the Japanese keyword hack walks through that variant on its own.
Remove the attacker's access to Search Console
Go to Settings > Users and permissions. Google says that if you find an owner you don't recognize, you might have been hacked. Remove them, and also delete their verification tokens (the meta tag, HTML file or DNS record they used to prove "ownership"). Google's help page is clear that a removed owner can re-verify as long as a token remains, so a leftover google1234abcd.html file in your root is an open invitation.
Then open the Sitemaps report. Delete any sitemap you didn't submit, and submit your clean one.
Make the spam URLs disappear
Google's docs say that already-indexed URLs returning a 4xx status are removed from the index. A 404 (not found) works. I prefer 410 (gone for good) because it's unambiguous. If the spam follows a pattern, one rule handles thousands of URLs. This example covers spam that lives on the homepage with a junk query string, like /?lgk=58201:
# Root .htaccess, above the "# BEGIN WordPress" block.
# Returns 410 Gone for homepage URLs with a short-letters=long-number query string.
# Could break: any real campaign or tracking URL that matches. Test against your
# Search Console and analytics URL lists before you leave it in place.
RewriteEngine On
RewriteCond %{QUERY_STRING} ^[a-z]{2,5}=[0-9]{4,}$
RewriteRule ^$ - [G,L]Adapt the pattern to what your site: search showed. Google's cleanup guide also warns against using the Removals tool on pages that were only damaged by the hacker. Use it only for spam pages you never want in search again, and treat it as temporary. My separate guide on deindexing spam URLs after a hack covers the full removal order.
Request the review
When the site is clean, back online and crawlable, request a review from the Security issues report. Google's stated timelines:
| Issue type | Typical review time (per Google) |
|---|---|
| Phishing | About a day |
| Malware | A few days |
| Hacked with spam | Up to several weeks |
Once Google confirms the site is clean, it says browser and search warnings come off within 72 hours. If you're staring at a specific red label right now, here's what each "this site may be hacked" style warning means.
When to stop and call someone
DIY works well for a single site with one obvious infection, SSH access and a free afternoon. It gets risky fast outside that box. I'd hand it off if any of these are true:
- It came back after you cleaned it. Something survived, and guessing again costs more time than it saves.
- You can't get SSH or WP-CLI. Doing all of the above by hand in a file manager is slow and easy to miss things in.
- The account holds several sites, or your host already suspended it.
- The site takes payments or stores customer data. Then it may be more than a cleanup, and you may need advice about notifying people. That part is a question for a lawyer, not a blog post.
- You can't tell a backdoor from a normal plugin file. No shame in that. It's a specialist skill.
That's the work I do through BigRed's hacked website repair service. It starts at $300 for a standard cleanup and $500 for an emergency. For emergency jobs, I start within a few hours during business hours (Pacific and Eastern time zones).
Final Thoughts
The commands above are the easy part. The order is what makes them stick: contain, files, hosting account, users and keys, then Google. Skip ahead to the review and you'll likely meet the same spam again, usually with a new set of URLs.
If you get halfway and want a second pair of hands, send me the details through the cleanup questionnaire. Tell me which step you reached and what you found, and I'll pick it up from there.
How to clean a hacked WordPress site: FAQs
Can I clean a hacked WordPress site myself?
Yes, if it's a single site, you have SSH or at least full file access, and you're comfortable reading a little PHP. Follow the order: contain, clean files and database, sweep the hosting account, reset users and keys, then deal with Google. Get help if the malware comes back, the account holds several sites, or the site handles payments or customer data.
Is a security plugin scan enough to fix a hacked WordPress site?
Usually not on its own. A plugin like a malware scanner is good at spotting known bad files, but it runs inside the same WordPress install the attacker controls. It won't check other sites in your hosting account, server cron jobs, SSH keys or rogue Search Console owners. Use it as one tool in step 2, not the whole cleanup.
Should I restore a backup instead of cleaning?
A backup from before the hack is a good starting point, and Google's own cleanup guide treats it as one. Check the date first, because many backups already contain the infection. After restoring, you still need to update everything, sweep the hosting account, rotate passwords and salts, and remove any attacker access in Search Console. Restoring alone doesn't close the hole.
How long does it take Google to remove the hacked warning?
Once you request a review, Google says malware reviews take a few days, phishing about a day, and spam hacks up to several weeks. After Google confirms the site is clean, warnings come off within 72 hours. Spam URLs returning 404 or 410 drop out of search results as Google recrawls them, which can also take a few weeks.
Why change passwords after the cleanup and not before?
Change your hosting login right away. For everything else, wait until the files are clean. If a backdoor is still running, it can read your new database password from wp-config.php or capture a new admin login, so changing them first just hands the attacker fresh keys. Rotate every password and the salts once the site is clean, and the attacker loses access for good.
Want a second pair of eyes on your site?
Tell me what's going on in the hacked website repair questionnaire. I reply with a plan and a fixed quote within one business day.