If your WordPress site has been hacked, work in this order: take it offline and change every password, copy the infected site for evidence, find how the attacker got in, then replace core, plugins and themes with clean copies instead of hunting for bad code. Only then verify what Google sees, harden the site and request a review. A hacked WordPress site is rarely destroyed; it is borrowed, kept working so you do not notice, and used to rank spam, redirect visitors or send email. The order decides whether the malware is back in a week. If you are not yet sure it is hacked, start with is my website hacked? and come back here with the evidence.
Quick answer
- Contain: maintenance mode, every password and key changed, unknown admin users removed.
- Preserve: a full copy of files and database as they are now, plus 30 days of access logs.
- Find the entry: outdated core, plugin or theme; a closed or abandoned plugin; a nulled theme; a weak password; a neighbour on the same server.
- Remove the malware by reinstalling: fresh core, plugins and themes from their source; keep only uploads (scanned); review
wp-config.phpand.htaccess; query the database for injected scripts. - Verify with the hacked site checker: the Googlebot and Google-referrer fetches must match the visitor fetch.
- Harden, request review in Search Console, and re-check in a week.
Why order matters
Two things go wrong in almost every failed WordPress malware removal. The first is cleaning before finding the entry: the injected files go, the hole stays, and the same script writes them back overnight, often through a cron entry or a backdoor in the uploads folder. The second is restoring a backup that is itself infected, because the injection sat there for weeks before anyone noticed.
Both are avoided by doing the boring steps first: preserving the evidence so you can find the entry, and reinstalling from sources you trust rather than editing the infected copy. Reinstalling feels drastic and is faster than reading every PHP file on the server.
How getReport checks it
The checker fetches the page three times: with getReport's normal user agent, with Googlebot's user agent, and as desktop Chrome with a Referer of https://www.google.com/. Each copy is reduced to a fingerprint: the visible text (hidden elements removed), the outbound links, spam vocabulary, runs of Japanese, Chinese, Korean or Cyrillic text on a page whose lang says otherwise, and any inline script that reads document.referrer and redirects. The three fingerprints are compared.

Cloaking is reported when at least 30% of the most frequent words in the Googlebot or Google-referrer copy are missing from the visitor copy and at least 20 new words appeared, or when foreign-script text appears only in the bot copy. Some sites vary content by user agent on purpose, so the finding shows the new words as evidence, not a verdict.
The redirect check compares where each fetch ends. It does not fetch with a mobile user agent, so a redirect that fires only on phones needs the manual check in step 5. The Japanese keyword hack and pharma and casino injections guides cover the two common shapes. The ordinary security findings point at the likely entry:
Step by step
1. Contain
Take the site out of circulation first, so the attacker's code cannot keep serving spam or stealing form input while you work: a maintenance page from the host panel, or a rule that lets only your IP through.
Then change every password that touches the site, in this order: hosting panel, SFTP or SSH, the database user (update wp-config.php to match), every WordPress administrator, and the API keys stored in plugins (payment gateways, mail services, CDN). Then look for administrators you did not create:
wp user list --role=administrator --fields=ID,user_login,user_email,user_registeredDelete unknown ones and reassign their content (wp user delete 17 --reassign=1). Rotate the salts in wp-config.php with wp config shuffle-salts to log everyone out.
2. Preserve the evidence
Before deleting anything, copy the web root and a database dump to a folder that is not web-accessible, plus the access and error logs for the last 30 days. You need them in step 3, and if customer data may have been read, for the disclosure you may be legally required to make.
mkdir -p ~/evidence-$(date +%F)
wp db export ~/evidence-$(date +%F)/db.sql
tar czf ~/evidence-$(date +%F)/files.tgz -C /var/www/example.com .
cp /var/log/apache2/access.log* ~/evidence-$(date +%F)/3. Find the entry
Most WordPress compromises come through one of five doors:
- An outdated or vulnerable plugin, theme or core. Plugins carry most published WordPress vulnerabilities; finding vulnerable and abandoned plugins shows how to match versions against the databases. A plugin closed on wordpress.org for a security issue is the first suspect.
- A nulled theme or plugin. Premium software from a "free" download site very often ships with a backdoor.
- Weak or reused passwords. A login called admin with a short password is brute-forced within days.
- A neighbour on shared hosting. If your files are clean and the entry is unexplained, ask the host.
- A compromised computer. Saved SFTP passwords on an infected laptop hand over every site at once.
The access log narrows it down. Look for POST requests to files that should never receive them, and for the first request to a file you did not create:
# POSTs to PHP files other than the normal entry points
grep -E 'POST .*\.php' access.log | grep -v -E 'wp-login\.php|admin-ajax\.php|wp-cron\.php|xmlrpc\.php' | head -50
# When did a suspicious file first appear in the log?
grep 'wp-content/uploads/2024/07/class-cache.php' access.log | head -3A spike of POSTs to wp-login.php or xmlrpc.php, then a successful login, points at a guessed password.
4. Remove the malware by reinstalling
Do not hunt for injections in 10,000 PHP files. Replace them with known-good copies and keep only what cannot be reinstalled.
- See the scale. WP-CLI checks core and plugin files against the checksums wordpress.org publishes. Every line it prints is a modified or added file; note them, they show where the attacker worked. Premium plugins have no published checksums and are reported as unverifiable.
wp core verify-checksums
wp plugin verify-checksums --allFresh core.
wp core download --force --skip-contentoverwriteswp-admin,wp-includesand the root files. It does not remove extra files, so runwp core verify-checksumsagain and delete anything it reports as "should not exist".Fresh plugins and themes. Reinstall each from wordpress.org (
wp plugin install <slug> --force) or from the vendor's download. Delete plugins and themes you do not use; an inactive plugin is still executable code.Uploads. Keep
wp-content/uploadsafter scanning it. There is no legitimate PHP file in uploads:
find wp-content/uploads -type f \( -name '*.php' -o -name '*.phtml' -o -name '*.php5' \) -print
# Recently modified PHP anywhere, excluding the freshly reinstalled folders
find . -name '*.php' -mtime -14 -not -path './wp-admin/*' -not -path './wp-includes/*' -printwp-config.php, .htaccess and mu-plugins. Attackers add an
includeof a file in uploads towp-config.php, and rewrite rules to.htaccessthat send Googlebot elsewhere. Anything that mentions a user agent, a referrer or an unknown.phpfile goes. Files inwp-content/mu-pluginsrun on every request and never appear in the plugin list.The database. Injections live in posts, options and cron:
-- Scripts or iframes inside post content
SELECT ID, post_title, post_type FROM wp_posts
WHERE post_content LIKE '%<script%' OR post_content LIKE '%<iframe%' OR post_content LIKE '%eval(%';
-- Options that carry scripts: widgets, theme settings
SELECT option_name FROM wp_options
WHERE option_value LIKE '%<script%' OR option_value LIKE '%base64_decode%' OR option_value LIKE '%eval(%';
-- The two addresses that a redirect hack rewrites
SELECT option_name, option_value FROM wp_options WHERE option_name IN ('siteurl', 'home');Then list scheduled jobs with wp cron event list --fields=hook,next_run_relative,recurrence, and delete hooks with random names or no plugin you can map them to (wp cron event delete <hook>).
5. Verify
Run the hacked site checker on the home page and three deep pages: an old post, a category page and a page Google listed with spam. The "Words not on the visitor page" share should be low in both right-hand columns, and hidden links, spam vocabulary, text in another script and the referrer redirect script should read 0 or none.
For the phone-only redirect the checker does not test:
curl -sI -A 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 Mobile/15E148 Safari/604.1' -e 'https://www.google.com/' https://example.com/ | grep -i -E '^(HTTP|location)'Then Search Console → Security issues, and a site:example.com search for the spam terms the report listed.
6. Harden
- Updates on: core minor releases automatically, plugins and themes weekly, no closed or abandoned plugin.
- Two-factor authentication for every administrator and a unique login name; WordPress login security has the setup.
- Least privilege: editors do not need administrator, and contractors' accounts go when the work ends.
- File permissions: directories 755, files 644,
wp-config.php600 or 640. - File editing off, so a stolen admin login cannot write PHP through the dashboard:
define('DISALLOW_FILE_EDIT', true);inwp-config.php. - No PHP execution in uploads:
# wp-content/uploads/.htaccess (Apache 2.4)
<FilesMatch "\.(php|phtml|php5|phar)$">
Require all denied
</FilesMatch># inside the server block, before the PHP location
location ~* /wp-content/uploads/.*\.(php|phtml|php5|phar)$ {
deny all;
}- A web application firewall in front, from the host or a CDN.
- Backups off the server, restored once to prove they work; the WordPress backup guide has the plan.
7. Request review and clean the index
In Search Console, Security issues → Request review, with two sentences on what was found and what was done. If Chrome shows a red warning, the same review clears Safe Browsing; flagged by Google Safe Browsing has the details and review times. Spam URLs that no longer exist should answer 404 or 410, not redirect to the home page. Use Removals for the worst ones while the recrawl catches up.
8. Monitor for re-infection
Re-run the hacked site checker on the same four pages after a week and after a month. In the access log, watch for POSTs to unexpected PHP files and requests for files you deleted. The log file analyser reads the log in your browser: a crawler still requesting the deleted spam folder is normal for a while, a 200 for it is not.
When to pay someone
Do the cleanup yourself when the site is a brochure or a blog and you have a clean backup or the patience to reinstall. Pay a specialist when the site is a shop or holds customer data, when the infection returns after a full reinstall, or when nobody available can read PHP.
Verify
- The hacked site checker on four pages: "Visitors and Googlebot see the same page text", "No spam links and no links served only to bots", "No redirect that fires only for Googlebot or Google visitors".
wp core verify-checksumsandwp plugin verify-checksums --allreport no modified files, andfind wp-content/uploads -name '*.php'prints nothing.wp user list --role=administratorlists only people you know;wp cron event listshows only hooks you can name.- Search Console → Security issues reads "No issues detected" after the review.
Common mistakes
- Cleaning without finding the entry. The files come back within days.
- Restoring the newest backup. It is often infected too. Restore an older one only if the log dates the compromise.
- Deleting the evidence first. Then you cannot find out how they got in, or what was exposed.
- Changing the WordPress passwords only. The database password, SFTP login and API keys are what the attacker usually kept.
- Redirecting removed spam URLs to the home page. Google treats it as a soft 404 and keeps them around longer.
Questions people ask
How do I remove malware from WordPress without a plugin?
Reinstall rather than scan. Download fresh WordPress core with WP-CLI, reinstall every plugin and theme from wordpress.org or the vendor, delete any PHP file in the uploads folder, and review wp-config.php, .htaccess and mu-plugins by hand. Then search the database for injected scripts and unknown cron jobs. Checksums from wordpress.org confirm the result.
Why does my WordPress site keep getting hacked?
Because the way in is still open, or a backdoor survived the cleanup. Common causes are a vulnerable plugin that was not updated or replaced, a password that was not changed (often SFTP or the database), a PHP file left in uploads, a malicious cron job, or another infected site on the same hosting account. Find the entry in the access log before cleaning again.
Should I restore a backup or clean a hacked WordPress site?
Restore only if you can date the break-in from the logs and have a backup from before it, then update everything before going live. Otherwise clean by reinstalling, because recent backups usually contain the same backdoor. Either way, change every password and close the entry first, or the restored site is reinfected the same way.
Do WordPress security plugins find all malware?
No. A scanner plugin runs inside the same WordPress install the attacker controls, so modified code can hide files from it, and it only knows the malware patterns in its signature list. Use it as one signal. Checksums compared from WP-CLI, a server-side scan from your host and an outside check of what Google sees catch what it misses.