To remove malware from WordPress, scan the files and database, replace WordPress core, plugins and themes with clean copies from official sources, delete every backdoor, clean injected content and rogue users from the database, then rotate passwords and security keys and patch the hole the attacker used. A cleanup that skips the database or the backdoors fails, because the attacker walks straight back in.
Before any of this, take the site offline with a 503 status, save a full copy of the infected files, database and logs as evidence, and change your hosting and admin passwords. That containment order is covered step by step in what to do in the first 24 hours after a hack; this guide picks up at the technical cleanup.
Step 1: Scan the WordPress site for malware
Scan from the server, not only from inside WordPress. Malware runs every time WordPress loads, so it can hide itself from a security plugin running in the same process.
Use these 3 types of scanner, and treat them as complementary:
- WP-CLI checksum verification: compares your files against the official WordPress.org copies.
- Server-side scanners: your host's malware scanner or a command-line scanner run over SSH, which reads files without executing them.
- Plugin scanners: security plugins that scan from inside WordPress, useful for ongoing monitoring once the site is clean.
Verify core and plugin checksums with WP-CLI
Run these 2 commands from the WordPress root over SSH:
wp core verify-checksums --include-root
wp plugin verify-checksums --all
The WP-CLI documentation for core verify-checksums (WordPress.org) states that it "verifies WordPress files against WordPress.org's checksums" and that --include-root warns about non-WordPress files in the root directory. Every "File doesn't verify against checksum" warning is a modified core file. The plugin verify-checksums documentation (WordPress.org) does the same for plugins, but only plugins with checksums on WordPress.org. Premium plugins, your theme, the uploads folder and the database are not checked at all.
List recently changed PHP files
Find files modified around the infection date and any PHP in the uploads folder:
find . -name "*.php" -mtime -14 -ls
find wp-content/uploads -name "*.php"
The uploads folder holds images and documents. A .php file there is almost always a backdoor.
Where WordPress malware hides
Malware hides in places a quick look misses, and most infections use more than one. Check every location below, even after you find the first infected file.
Files
Check these 6 file locations first:
wp-config.php: injectedincludelines or obfuscated code above the database settings, often usingeval,base64_decodeorgzinflate..htaccess: rewrite rules that redirect visitors from Google or mobile devices to spam sites, in the root and in subfolders.wp-content/uploads/: PHP files disguised with image-like names, for examplelogo.phpor2024/05/cache.php.wp-content/mu-plugins/: must-use plugins load automatically on every request and do not appear in the normal Plugins screen.wp-includes/andwp-admin/: extra files with plausible names that the checksum command flags as non-WordPress items.- Theme files: code added to
functions.php,header.phporfooter.php, which no checksum covers.
Database
Search the database as carefully as the files. Attackers store payloads there because a file-only cleanup never touches it.
Check these 4 places:
siteurlandhomeinwp_options. Changed values redirect the whole site. Check withwp option get siteurl.- Post and widget content. Search for injected scripts with
wp db search '<script' --all-tablesand repeat foreval(andbase64_decode, which the WP-CLI db search reference (WordPress.org) supports across every table. - Administrator accounts. List them with
wp user list --role=administrator --fields=ID,user_login,user_email,user_registeredand look for names created on or after the infection date. - Scheduled cron events. List them with
wp cron event listand look for hooks that do not belong to any installed plugin. Rogue cron jobs re-create deleted backdoors on a timer.
A WP-CLI detail most cleanup guides miss
Most WP-CLI commands load WordPress, so they also run the malware, which can hide its own admin user from wp user list. Add --skip-plugins --skip-themes to every command during cleanup. The WP-CLI global parameters reference (WordPress.org) notes a catch: with --skip-plugins, "mu-plugins are still loaded." Empty the mu-plugins folder of anything you did not install before trusting any other output.
Step 2: Replace core, plugins and themes from clean sources
Replace every file you did not write rather than trying to clean it line by line. A fresh copy is faster and leaves nothing behind.
Replace them in this order:
- Core: delete the
wp-adminandwp-includesfolders and the root PHP files exceptwp-config.php, then runwp core download --skip-content --force --version=<your version>. Deleting first matters, because--forceoverwrites existing files but does not remove extra ones. - Plugins: delete each plugin folder and reinstall from WordPress.org or the vendor's account dashboard. Never reinstall from a zip file found on the server.
- Themes: reinstall the parent theme fresh. Compare a custom or child theme against version control, or read it line by line.
- Unused code: delete inactive plugins and themes entirely. Inactive plugin files are still reachable by URL.
Clean wp-config.php by hand: copy only the database credentials and table prefix into the default wp-config-sample.php and discard the old file.
Step 3: Clean the database
Remove injected content and rogue records directly, after backing up the database again. Delete unknown administrators, reassigning their content to a known user, and delete cron events you traced to the malware.
For injected scripts in posts, a targeted replace fixes hundreds of posts at once. Run it with --dry-run first to see what changes:
wp search-replace '<script src="https://bad-domain.example/x.js"></script>' '' --dry-run --skip-plugins --skip-themes
Then check the options table for unfamiliar autoloaded entries and for widget settings containing JavaScript.
Step 4: Rotate salts, keys and every credential
Rotate the security keys so every stolen login cookie stops working. The wp-config.php documentation (WordPress.org) states that you "can change these at any point in time to invalidate all existing cookies," which means "all users will have to login again." Generate new values from the WordPress.org secret-key service or run:
wp config shuffle-salts
Then change the database password and update wp-config.php, reset every administrator password, and replace SFTP passwords, SSH keys and API keys stored on the server.
Step 5: Fix file permissions and harden the install
Set permissions so the web server cannot rewrite its own code. WordPress's hardening guide (WordPress.org) gives 755 for directories and 644 for files, and 400 or 440 for wp-config.php so only you and the web server can read it.
Apply these 3 hardening changes as well:
- Disable the built-in file editor with
define( 'DISALLOW_FILE_EDIT', true );inwp-config.php. - Block PHP execution in uploads with the
.htaccessfile below, placed insidewp-content/uploads/on Apache. - Enable two-factor authentication for every administrator account.
<FilesMatch "\.(php|phtml|php[0-9])$">
Require all denied
</FilesMatch>
On Nginx, add a location rule that denies .php requests under /wp-content/uploads/ instead, because Nginx ignores .htaccess files.
How to prevent reinfection
Prevent reinfection by closing the entry point, which on WordPress is most often a vulnerable plugin. Patchstack's State of WordPress Security in 2026 (Patchstack, 2026) counted 11,334 new WordPress vulnerabilities in 2025, 91% of them in plugins, 9% in themes and only 6 in core. The same report found 46% had no patch at the time of disclosure and that the weighted median time to first exploit for heavily exploited flaws was 5 hours.
Those numbers set the defence. Keep these 4 controls running after the cleanup:
- Automatic updates for plugins with a good patch record.
- A virtual patching firewall that blocks exploits for vulnerabilities with no vendor fix.
- File integrity monitoring that alerts on any new or changed PHP file.
- Daily off-site backups kept for at least 30 days, so a clean restore point exists next time.
Check what Google sees, not only what you see
A cleaned WordPress site still ranks badly when spam pages remain in Google's index. This is where security and SEO meet. Many WordPress infections cloak spam, serving it only to Googlebot, so use the URL Inspection tool in Search Console to view the page as Google fetched it. Thousands of junk URLs in a site: search point to the Japanese keyword hack, which needs its own cleanup of the index after the files are fixed.
When restoring a backup is faster than cleaning
Restore a backup when you hold one dated before the infection and the site changes rarely. A restore replaces every file and table in one step, while a manual cleanup depends on finding every backdoor by hand.
Choose a restore if all 3 conditions hold:
- The backup predates the earliest modified file or suspicious log entry.
- The content added since the backup is small enough to re-enter by hand.
- You patch the vulnerable plugin immediately after restoring.
Clean manually if the infection date is unknown, if every backup is infected, or if the site holds orders and form entries you cannot lose. A restore without the patch brings the same infection back within days.
When to hire help with a WordPress malware cleanup
Hire help when the infection returns after a cleanup, when you cannot use SSH and WP-CLI, or when the site handles payments or customer data. Reinfection means a backdoor or entry point was missed, and customer data means you need to know what the attacker accessed, not only what they changed.
I have done security research since 2011, with acknowledgements from Google, BlackBerry and TeamViewer, and I write PHP in production, so I read the injected code itself to see what it did. If you are comparing who to bring in for the SEO side of the recovery, my guide to hiring a technical SEO consultant explains what that role covers.
Frequently asked questions
How do I know if my WordPress site is infected?
Common signs are redirects to unrelated sites, a "This site may be hacked" label in Google, a browser malware warning, unknown administrator accounts and PHP files in the uploads folder. Running wp core verify-checksums confirms whether core files were modified.
Can a security plugin remove WordPress malware on its own?
A security plugin removes known malware signatures but misses custom backdoors, rogue cron jobs and database injections. It also runs inside an infected WordPress, so malware in mu-plugins or core files can interfere with it.
Will reinstalling WordPress remove the malware?
Reinstalling core removes malware in wp-admin and wp-includes only. Backdoors in wp-config.php, wp-content, the uploads folder, themes, plugins and the database survive a core reinstall.
Should I change the WordPress salts after a hack?
Yes, change the salts and security keys in wp-config.php after every hack. New keys invalidate all existing login cookies, so an attacker holding a stolen session is signed out.
How long does it take to clean a hacked WordPress site?
A restore from a clean backup is the fastest route, because it replaces every file and table in one step. A manual cleanup takes longer the more backdoors exist and the more the database is infected, so plan for scanning, replacement, database work and hardening as separate stages.
