
Remove WordPress Malware Manually: Technical Guide
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
| Tool | Usage | Access |
|---|---|---|
| SSH | Command-line server access | Terminal / PuTTY |
| SFTP | Secure file transfer | FileZilla / WinSCP |
| phpMyAdmin | Database administration | Hosting panel |
| WP-CLI | WordPress command-line interface | Server installation |
| Text editor | File analysis | nano / 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.sqlPut 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/.maintenanceStep 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.phpSearch for and remove:
- Any code before
<?phpat the beginning of the file - Lines containing
eval(),base64_decode(),str_rot13() require()orinclude()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.phpRegenerate 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/.htaccessLook for:
RewriteRuledirectives redirecting to external domains<FilesMatch>blocks hiding PHP filesphp_value auto_prepend_fileinstructions 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 WordPressClean 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
doneClean 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/htmlFor 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/htmlClean 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.txtStep 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/htmlUpdate 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/htmlReset 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/htmlStep 7: Final Verification and Checklist
Before bringing the site back online, perform a complete verification:
Verification Checklist
| Check | Command | Status |
|---|---|---|
| Core checksums | wp core verify-checksums | To verify |
| Plugin checksums | wp plugin verify-checksums --all | To verify |
| PHP files in uploads | find uploads/ -name "*.php" | Must be empty |
| Unknown admin accounts | SQL query on wp_users | To verify |
| Suspicious cron jobs | wp cron event list | To verify |
| Clean .htaccess | Manual inspection | To verify |
| Clean wp-config.php | Manual inspection | To verify |
| Security keys regenerated | wp config shuffle-salts | To verify |
| Passwords changed | All administrators | To 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,vtRemove Maintenance Mode
# Delete the maintenance file
rm /var/www/html/.maintenance
# Flush the cache
wp cache flush --path=/var/www/htmlPost-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:
- Log in to Google Search Console
- Navigate to Security & Manual Actions
- Click Request a review
- 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.
