Back to blog
Remove WordPress Malware Manually: Technical Guide
SEO

Remove WordPress Malware Manually: Technical Guide

Bastien AllainMarch 11, 202612 min read
wordpressmalwaremanual-removalsecuritywp-cli

When a WordPress site is infected, the most reliable solution is often manual cleanup. Automated security plugins do not always detect sophisticated backdoors or obfuscated injections. This technical guide walks you through the step-by-step process of manually removing WordPress malware, using SSH commands, SQL queries and WP-CLI to restore a clean and secure site. Every command is documented so you can intervene with precision, even on complex infections.

Prerequisites and Environment Preparation

Before starting the cleanup, you need to prepare your working environment. A poorly prepared intervention can worsen the situation or cause data loss.

Required Tools

ToolUsageAccess
SSHCommand-line server accessTerminal / PuTTY
SFTPSecure file transferFileZilla / WinSCP
phpMyAdminDatabase administrationHosting panel
WP-CLIWordPress command-line interfaceServer installation
Text editorFile analysisnano / vim / VS Code

Create a Full Backup

The absolute first step is to back up the entire site, even if infected. You will need this backup as a reference if the cleanup goes wrong.

# SSH into your server
ssh user@your-server.com
 
# Create a backup directory
mkdir -p ~/backups/$(date +%Y%m%d)
 
# Back up all WordPress files
tar -czf ~/backups/$(date +%Y%m%d)/files-backup.tar.gz /var/www/html/
 
# Back up the database
mysqldump -u db_user -p db_name > ~/backups/$(date +%Y%m%d)/database-backup.sql

Put the Site in Maintenance Mode

To prevent visitors from accessing the infected site and block malware propagation:

# Create a .maintenance file at the WordPress root
echo '<?php $upgrading = time(); ?>' > /var/www/html/.maintenance

Step 1: Identify Malicious Files

Detection is the most critical step. WordPress malware uses obfuscation techniques to evade standard scans. Here are the essential grep commands to locate suspicious files.

Search for Dangerous PHP Functions

# Search for eval() with base64_decode - classic malware signature
grep -rl "eval(base64_decode" /var/www/html/wp-content/ --include="*.php"
 
# Search for common obfuscation functions
grep -rl "str_rot13\|gzinflate\|gzuncompress\|str_replace.*chr(" /var/www/html/wp-content/ --include="*.php"
 
# Search for dangerous system calls
grep -rl "exec(\|system(\|passthru(\|shell_exec(\|popen(" /var/www/html/wp-content/ --include="*.php"
 
# Search for remote file inclusions
grep -rl "file_get_contents.*http\|curl_exec\|wp_remote_get.*base64" /var/www/html/wp-content/ --include="*.php"

Detect Recently Modified Files

Malware often modifies existing files, as detailed in our analysis of the most targeted WordPress files. Search for recently changed files:

# PHP files modified in the last 7 days
find /var/www/html/ -name "*.php" -mtime -7 -type f
 
# PHP files modified in the last 24 hours
find /var/www/html/ -name "*.php" -mtime -1 -type f
 
# Files with suspicious names (random characters)
find /var/www/html/wp-content/ -name "*.php" -regex ".*[a-z0-9]\{8,\}\.php"

Identify Files That Do Not Belong to Core

# Download a clean copy of WordPress
cd /var/tmp && wget https://wordpress.org/latest.tar.gz && tar -xzf latest.tar.gz
 
# Compare core files with the clean version
diff -rq /var/www/html/wp-admin/ /PATH/TO/CLEAN-WP/wp-admin/
diff -rq /var/www/html/wp-includes/ /PATH/TO/CLEAN-WP/wp-includes/

Files present in your installation but absent from the clean version are potentially injected malicious files.

Step 2: Clean WordPress Core Files

The WordPress core (the wp-admin/ and wp-includes/ directories) should never be modified. The safest method is to replace them entirely.

Replace wp-admin and wp-includes

# Remove existing core directories
rm -rf /var/www/html/wp-admin/
rm -rf /var/www/html/wp-includes/
 
# Copy clean files
cp -r /PATH/TO/CLEAN-WP/wp-admin/ /var/www/html/wp-admin/
cp -r /PATH/TO/CLEAN-WP/wp-includes/ /var/www/html/wp-includes/
 
# Copy root core files (except wp-config.php)
cp /PATH/TO/CLEAN-WP/wp-*.php /var/www/html/
cp /PATH/TO/CLEAN-WP/index.php /var/www/html/
cp /PATH/TO/CLEAN-WP/xmlrpc.php /var/www/html/

Clean wp-config.php

The wp-config.php file is a prime target for attackers. Inspect it line by line:

# Display wp-config.php contents
cat -n /var/www/html/wp-config.php

Search for and remove:

  • Any code before <?php at the beginning of the file
  • Lines containing eval(), base64_decode(), str_rot13()
  • require() or include() pointing to unknown files
  • Unexpected WordPress constants added at the end of the file
# Check for suspicious inclusions in wp-config.php
grep -n "eval\|base64_decode\|require.*\.php\|include.*\.php" /var/www/html/wp-config.php

Regenerate security keys by replacing the AUTH_KEY, SECURE_AUTH_KEY, etc. lines with fresh values generated at api.wordpress.org/secret-key.

Clean .htaccess

The .htaccess file is frequently used for malicious redirects:

# Display .htaccess contents
cat -n /var/www/html/.htaccess

Look for:

  • RewriteRule directives redirecting to external domains
  • <FilesMatch> blocks hiding PHP files
  • php_value auto_prepend_file instructions loading malicious code

Replace the content with the default WordPress .htaccess:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

Clean index.php

The root index.php file should only contain:

<?php
/**
 * Front to the WordPress application.
 */
define( 'WP_USE_THEMES', true );
require __DIR__ . '/wp-blog-header.php';

Any additional code is suspicious and should be removed.

Step 3: Clean the wp-content Directory

The wp-content/ directory contains your themes, plugins and uploads. This is the most complex area to clean because it mixes legitimate third-party code with malware.

Clean Plugins

# List all PHP files in plugins
find /var/www/html/wp-content/plugins/ -name "*.php" | head -50
 
# Search for malicious code in plugins
grep -rl "eval(base64_decode\|eval(gzinflate\|eval(str_rot13" /var/www/html/wp-content/plugins/ --include="*.php"

The most reliable method: delete and reinstall each plugin from the official WordPress repository:

# With WP-CLI - reinstall all plugins from the repository
wp plugin list --path=/var/www/html --format=csv | tail -n +2 | while IFS=',' read name status update version; do
  wp plugin install "$name" --force --path=/var/www/html
done

Clean Themes

Apply the same logic to themes:

# Reinstall the active theme from the repository
wp theme install $(wp theme list --status=active --field=name --path=/var/www/html) --force --path=/var/www/html

For custom themes not available on the repository, inspect each PHP file:

# Scan the active theme for suspicious code
grep -rn "eval(\|base64_decode\|str_rot13\|gzinflate" /var/www/html/wp-content/themes/your-theme/ --include="*.php"

Clean the Uploads Directory

The uploads/ directory should contain no PHP files:

# Find all PHP files in uploads (all suspicious)
find /var/www/html/wp-content/uploads/ -name "*.php" -type f
 
# Delete all PHP files in uploads
find /var/www/html/wp-content/uploads/ -name "*.php" -type f -delete
 
# Also search for files with double extensions
find /var/www/html/wp-content/uploads/ -name "*.php.*" -o -name "*.phtml" -o -name "*.php5"

Check Suspicious Directories

# Look for hidden or unusually named directories
find /var/www/html/wp-content/ -type d -name ".*"
find /var/www/html/wp-content/ -type d -name "cache" -o -name "tmp" -o -name "sessions"
 
# List contents of suspicious directories
ls -la /var/www/html/wp-content/mu-plugins/

The mu-plugins/ directory is often used to hide backdoors because must-use plugins are loaded automatically without activation.

Step 4: Clean the Database

Malware injects code into the WordPress database, primarily in the wp_options, wp_posts and wp_postmeta tables.

Search for Injections in wp_options

Connect to phpMyAdmin or use the MySQL command line:

-- Search for injected scripts in options
SELECT option_name, LEFT(option_value, 200)
FROM wp_options
WHERE option_value LIKE '%<script%'
   OR option_value LIKE '%eval(%'
   OR option_value LIKE '%base64_decode%'
   OR option_value LIKE '%document.write%';
 
-- Search for unknown admin users
SELECT * FROM wp_users WHERE user_login NOT IN ('admin', 'your_login');
 
-- Check user roles
SELECT u.user_login, m.meta_value
FROM wp_users u
JOIN wp_usermeta m ON u.ID = m.user_id
WHERE m.meta_key = 'wp_capabilities'
AND m.meta_value LIKE '%administrator%';

Search for Injections in Posts

-- Posts containing malicious JavaScript
SELECT ID, post_title, LEFT(post_content, 200)
FROM wp_posts
WHERE post_content LIKE '%<script%'
   OR post_content LIKE '%<iframe%'
   OR post_content LIKE '%eval(%'
   OR post_content LIKE '%document.createElement%';
 
-- Search in post metadata
SELECT post_id, meta_key, LEFT(meta_value, 200)
FROM wp_postmeta
WHERE meta_value LIKE '%<script%'
   OR meta_value LIKE '%base64_decode%';

Remove Fraudulent Admin Accounts

-- Identify suspicious accounts
SELECT u.ID, u.user_login, u.user_email, u.user_registered
FROM wp_users u
JOIN wp_usermeta m ON u.ID = m.user_id
WHERE m.meta_key = 'wp_capabilities'
AND m.meta_value LIKE '%administrator%'
ORDER BY u.user_registered DESC;
 
-- Delete a fraudulent user (replace the ID)
DELETE FROM wp_usermeta WHERE user_id = SUSPECT_ID;
DELETE FROM wp_users WHERE ID = SUSPECT_ID;

Clean Malicious Cron Jobs

Attackers often schedule cron jobs to re-infect the site:

-- Display cron jobs
SELECT option_value FROM wp_options WHERE option_name = 'cron';

With WP-CLI:

# List all cron events
wp cron event list --path=/var/www/html
 
# Delete a suspicious cron event
wp cron event delete event_name --path=/var/www/html

Clean Malicious Transients

-- Search for suspicious transients
SELECT option_name, LEFT(option_value, 200)
FROM wp_options
WHERE option_name LIKE '%_transient_%'
AND (option_value LIKE '%<script%'
     OR option_value LIKE '%eval(%'
     OR LENGTH(option_value) > 50000);
 
-- Delete all expired transients
DELETE FROM wp_options
WHERE option_name LIKE '%_transient_%';

Step 5: Remove Backdoors

Backdoors are hidden access points that allow the attacker to return even after cleanup. They are the primary reason why sites get re-infected.

Common Backdoor Signatures

# Backdoors using callback functions
grep -rl "preg_replace.*\/e\|create_function\|call_user_func\|array_map.*base64" /var/www/html/wp-content/ --include="*.php"
 
# Backdoors using global variables
grep -rl "\$_GET\[.*\]\|\$_POST\[.*\]\|\$_REQUEST\[.*\]" /var/www/html/wp-content/ --include="*.php" | head -20
 
# Backdoors using websockets or reverse connections
grep -rl "fsockopen\|socket_create\|stream_socket_client" /var/www/html/wp-content/ --include="*.php"
 
# Obfuscated code with character concatenations
grep -rl "chr(.*)\.\s*chr(\|\\\\x[0-9a-f]\{2\}" /var/www/html/wp-content/ --include="*.php"

Check Server Configuration Files

# Search for .htaccess files in subdirectories
find /var/www/html/ -name ".htaccess" -type f
 
# Search for malicious php.ini files
find /var/www/html/ -name "php.ini" -o -name ".user.ini" | xargs cat
 
# Check auto_prepend files
grep -r "auto_prepend_file\|auto_append_file" /var/www/html/ --include=".htaccess" --include="php.ini" --include=".user.ini"

Search for Webshells

# Classic webshells
grep -rl "FilesMan\|WSO\|c99shell\|r57shell\|b374k" /var/www/html/ --include="*.php"
 
# PHP files with deceptive names in wp-includes
find /var/www/html/wp-includes/ -name "*.php" | sort > /PATH/TO/current_includes.txt
find /PATH/TO/CLEAN-WP/wp-includes/ -name "*.php" | sort > /PATH/TO/clean_includes.txt
diff /PATH/TO/current_includes.txt /PATH/TO/clean_includes.txt

Step 6: Verification with WP-CLI

WP-CLI has built-in commands to verify WordPress file integrity.

Checksum Verification

# Verify core file integrity
wp core verify-checksums --path=/var/www/html
 
# Verify plugin integrity
wp plugin verify-checksums --all --path=/var/www/html
 
# List inactive plugins (potentially malicious)
wp plugin list --status=inactive --path=/var/www/html

Update WordPress and Extensions

After cleanup, update everything to the latest versions. A WordPress maintenance contract ensures these updates are performed regularly to prevent reinfection:

# Update WordPress core
wp core update --path=/var/www/html
 
# Update all plugins
wp plugin update --all --path=/var/www/html
 
# Update all themes
wp theme update --all --path=/var/www/html
 
# Remove unused themes and plugins
wp plugin delete $(wp plugin list --status=inactive --field=name --path=/var/www/html) --path=/var/www/html
wp theme delete $(wp theme list --status=inactive --field=name --path=/var/www/html) --path=/var/www/html

Reset Passwords

# Change password for all administrators
wp user list --role=administrator --field=user_login --path=/var/www/html | while read user; do
  wp user update "$user" --user_pass="$(openssl rand -base64 24)" --path=/var/www/html
  echo "Password changed for: $user"
done
 
# Regenerate security keys
wp config shuffle-salts --path=/var/www/html

Step 7: Final Verification and Checklist

Before bringing the site back online, perform a complete verification:

Verification Checklist

CheckCommandStatus
Core checksumswp core verify-checksumsTo verify
Plugin checksumswp plugin verify-checksums --allTo verify
PHP files in uploadsfind uploads/ -name "*.php"Must be empty
Unknown admin accountsSQL query on wp_usersTo verify
Suspicious cron jobswp cron event listTo verify
Clean .htaccessManual inspectionTo verify
Clean wp-config.phpManual inspectionTo verify
Security keys regeneratedwp config shuffle-saltsTo verify
Passwords changedAll administratorsTo verify

Scan with an External Tool

After manual cleanup, validate with an external scan:

# Scan with ClamAV if available
clamscan -r /var/www/html/ --infected --remove=no
 
# Use WPScan for additional verification
wpscan --url https://your-site.com --enumerate vp,vt

Remove Maintenance Mode

# Delete the maintenance file
rm /var/www/html/.maintenance
 
# Flush the cache
wp cache flush --path=/var/www/html

Post-Cleanup Actions

The cleanup is complete, but your work does not stop here. Several actions are necessary to prevent re-infection.

Request a Google Review

If your site was flagged as dangerous by Google:

  1. Log in to Google Search Console
  2. Navigate to Security & Manual Actions
  3. Click Request a review
  4. Describe the cleanup actions taken

Monitor the Site

# Create a basic monitoring script
cat > /usr/local/bin/wp-monitor.sh << 'SCRIPT'
#!/bin/bash
SITE_PATH="/var/www/html"
LOG="/var/log/wp-monitor.log"
 
# Verify checksums
wp core verify-checksums --path=$SITE_PATH >> $LOG 2>&1
 
# Check files modified in the last 24h
find $SITE_PATH -name "*.php" -mtime -1 >> $LOG 2>&1
 
# Check for PHP files in uploads
find $SITE_PATH/wp-content/uploads/ -name "*.php" >> $LOG 2>&1
 
echo "--- Scan completed on $(date) ---" >> $LOG
SCRIPT
 
chmod +x /usr/local/bin/wp-monitor.sh
 
# Add to crontab (daily scan at 3 AM)
echo "0 3 * * * /usr/local/bin/wp-monitor.sh" | crontab -

When to Call an Expert

Manual cleanup is effective for standard infections, but some situations go beyond the scope of a self-guided intervention:

  • Persistent infections that return despite multiple cleanups
  • Server-level compromise (rootkit, unauthorized SSH access)
  • E-commerce sites where sensitive data may have been exposed
  • Polymorphic malware that changes signature with every scan
  • Blacklisting by Google or browsers that persists after cleanup

Our team handles complex infections with an in-depth cleanup protocol and a no-reinfection guarantee. Discover our WordPress malware removal service or explore our WordPress security solutions for proactive protection.

For further reading, check our guide on signs your WordPress site has been hacked and our catalog of common WordPress malware in 2026.

Related posts