Hacked WordPress cleanup checklist: every place malware hides
Updated 2026-10-16 · 10 min read · by the IsHacked team
The visible symptom (spam, redirects, a blank page) is the smallest part of an infection. What matters is the persistence: the pieces that put the malware back after you delete it. This checklist comes from real cleanups where reinfection happened every hour until the last piece was found.
Before you start
- Take a full backup of files and database, even of the infected state. You may need it to trace what happened.
- If you can, put the site in maintenance mode or block wp-admin by IP while you work.
- Work from a terminal with WP-CLI where possible: it doesn't run the infected theme or plugins.
1. Files
- Core:
wp core verify-checksumslists modified and extra core files. Reinstall core withwp core download --force --skip-content. Watch for extra files inwp-includes, such aswp-includes/assets/class-walker_1a2b3c4d.php. - Plugins:
wp plugin verify-checksums --allfor plugins from wordpress.org. Reinstall premium plugins from the vendor. Delete anything you don't recognise; fake plugins use names likemedia-optimization-core-3f2a1bor "WP Core Object Cache". - Must-use plugins: everything in
wp-content/mu-pluginsruns on every request and doesn't appear in the plugin list. Recent infections dropped dozens of files named likewp-rest-handler-1a2b3c4d.phpthere. - Drop-ins:
wp-content/object-cache.php,advanced-cache.phpanddb.phpload before plugins. Make sure each belongs to a cache plugin you actually use. - Uploads: there should be no PHP in
wp-content/uploads:find wp-content/uploads -name '*.php'. Also look for PHP disguised as images in form-upload folders (for exampleuploads/ninja-forms/tmp). - Theme: compare with a fresh copy, especially
functions.php,header.phpandfooter.php. - Web root: unexpected
index.html,.htaccessrules,wp-config.php.bakor.savecopies, and SQL dumps.
2. Database
- Users:
wp user list --role=administrator. Delete anyone you don't know, and checkwp_usermetafor capabilities granted to unexpected users. - Options: check
siteurlandhome, and look for large or odd entries, including transients: payloads have been stored in options such as_site_transient_health_…. - Content: search posts, widgets and theme options for
<script,eval(,base64and unfamiliar domains. - Cron:
wp cron event list. Hooks with random suffixes that run hourly are a typical way to rewrite deleted files.
3. Server
- Server crontab (
crontab -lfor the site's user): entries that download or call files every few minutes. - Other sites on the same account. Cross-contamination between sites sharing a hosting account is common.
- Access logs, to find the entry point: an outdated plugin, an upload form, a leaked password.
4. Credentials
- Change all WordPress passwords and force a logout by replacing the salts in
wp-config.php. - Change the database password, hosting panel, SFTP/SSH and any API keys stored on the site.
- Remove unknown SSH keys and hosting users.
5. Close the hole
- Update WordPress, every plugin and theme; remove the ones you don't use.
- Upgrade PHP to a supported version.
- Block PHP execution in
wp-content/uploads, and disable XML-RPC if nothing needs it.
6. Search engines and email
- In Search Console, remove unknown owners and sitemaps, then request a review if the site was flagged.
- Check the domain and server IP against blocklists, and request delisting once the site is clean.
7. Watch for the comeback
Reinfection usually shows up within days. Check the site every day from outside: as Googlebot, as a phone from Google, the sitemap, new users, new script sources and new plugins. Start with a free check
FAQ
Is restoring a backup enough?
Only if you are sure the backup predates the infection and you also close the hole that let the attacker in and change every password. Otherwise the site is reinfected, often within hours. In one cleanup we worked on, the malware came back right after a restore.
Why does the malware keep coming back?
Because something re-creates it: a cron job, a must-use plugin, an object-cache drop-in, a payload stored in the database, or a server crontab entry. Removing visible files without removing the persistence doesn't work.