Clean a Hacked WordPress Site: Malware Removal, Backups, and Hardening
Discovering that your WordPress website has been hacked is one of the most stressful experiences a site owner or developer can face. However, panicking will not resolve the issue. By executing a structured, step-by-step forensic analysis and cleanup strategy, you can eliminate malicious code, identify the vulnerability entry points, restore database integrity, and harden your platform against future attacks. This guide outlines the exact processes, terminal commands, and database queries required to clean a compromised WordPress installation in 2026.
Explore our professional WordPress security services to secure your site against vulnerabilities.
Executing this security hardening workflow requires a systematic approach that balances technical optimization with content quality. Here is how to execute each strategy effectively.
1. Common Entry Points and Explanations of Exploitation
Malicious bots scour the web for known vulnerabilities, brute-force admin credentials, and exploit misconfigured webservers. To clean a site, you must first understand how the attacker gained access.
1. Outdated Plugins and Themes
Vulnerabilities like SQL Injection (SQLi), Local File Inclusion (LFI), and Remote Code Execution (RCE) are frequently discovered in outdated plugins. Attackers use automated tools to scan sites for known vulnerable plugin versions and execute payloads. Two specific vulnerability classes are particularly dangerous:
- PHP Object Injection (Deserialization): Occurs when user-supplied input is passed directly to PHP’s
unserialize()function. If the codebase contains suitable “pop chain” helper classes, attackers can instantiate arbitrary PHP objects, execute code, and write files to the system. - Unrestricted File Uploads: If a plugin does not correctly validate files uploaded via custom forms (e.g., checks only file extension mime-types on the client side while ignoring backend verification), an attacker can upload a web shell script disguised as an image, saving it directly to
/wp-content/uploads/where they can execute it via direct HTTP access.
2. Weak Admin Credentials and Brute-Force Attacks
Bots continuously hit /wp-login.php and /xmlrpc.php with lists of common passwords. If your admin account uses a weak password, it will eventually be compromised.
3. XML-RPC Exploitation
The legacy xmlrpc.php file allows developers to interact with the WordPress API via XML. Because it supports multi-call requests, an attacker can test hundreds of credential combinations in a single HTTP request, bypassing standard login limits.
4. Shared Hosting Cross-Contamination
If your site is hosted on a shared server, a vulnerability on a neighbor’s site can allow an attacker to write files across the directory tree, infecting your clean files.
2. Auditing Webserver Access Logs via SSH
To pinpoint the entry point and time of the hack, use SSH commands to audit your webserver access logs. This allows you to find malicious POST requests that correspond to file modification timestamps.
Identifying Attack Signatures in Nginx or Apache Logs
Run these shell scripts in your terminal to parse your logs for common attack patterns:
# Find IP addresses making high-frequency POST requests to login or XML-RPC endpoints
grep -h "POST /wp-login.php" /var/log/nginx/access.log* | awk '{print $1}' | sort | uniq -c | sort -nr | head -n 20
grep -h "POST /xmlrpc.php" /var/log/nginx/access.log* | awk '{print $1}' | sort | uniq -c | sort -nr | head -n 20
# Search access logs for exploitation attempts on known attack endpoints
grep -E "wp-config\.php|\.env|eval\(|union\+select" /var/log/nginx/access.log
# Extract requests made by an IP address that modified files during the hack window
grep "192.168.1.100" /var/log/nginx/access.log | grep -E "POST|PUT"
Identifying the source IP and the exact script that processed the request reveals the exploited plugin or theme, allowing you to patch the vulnerability.
3. Step-by-Step Triage and Forensic Diagnostics
Before deleting files, preserve the state of the installation. This allows you to restore to the initial state if needed.
Step 1: Export a Forensic Backup
Create a tar archive of your entire directory tree and export your database using WP-CLI:
# Create file system archive
tar -czf /tmp/compromised-files-$(date +%Y%m%d).tar.gz -C /var/www/html .
# Export database
wp db export /tmp/compromised-db-$(date +%Y%m%d).sql --path=/var/www/html
Step 2: System File Inspection
Use find and grep to scan for modified files, PHP code within uploads folders, and typical backdoor signatures (e.g., eval, base64_decode, or hidden event triggers):
# Locate PHP files inside the uploads folder (there should be none)
find /var/www/html/wp-content/uploads/ -type f -name "*.php"
# Find PHP files modified within the last 7 days
find /var/www/html/ -type f -name "*.php" -mtime -7
# Search for common obfuscation functions used by hackers
grep -rn "base64_decode(" /var/www/html/wp-content/
grep -rn "eval(" /var/www/html/wp-content/
grep -rn "gzinflate(" /var/www/html/wp-content/
4. Cleaning the Filesystem and Re-installing WordPress Core
Do not rely on security scanners to clean core files. The most reliable method is to replace the files with clean copies from the official WordPress repository.
+-------------------------------------------------+
| Infected WordPress Installation Directory |
+-------------------------------------------------+
|
[ Run Core Replace Command ]
|
+-------------------------v-------------------------+
| wp core download --force --skip-content --path=. |
+---------------------------------------------------+
|
+----------------------v----------------------+
| Clean core dirs: wp-admin/ & wp-includes/ |
+---------------------------------------------+
Replacing Core System Files
Use WP-CLI to replace all root system files and directories (wp-admin/ and wp-includes/) with clean originals, leaving your content folder untouched:
# Replace core system files
wp core download --force --skip-content --path=/var/www/html
# Validate core integrity using checksums
wp core verify-checksums --path=/var/www/html
Re-installing Plugins and Themes
Delete and reinstall all plugins to ensure no backdoors remain hidden inside their directories:
# List all active plugins
wp plugin list --status=active --path=/var/www/html
# Force reinstall all active repository plugins
wp plugin list --field=name --path=/var/www/html | xargs -I {} wp plugin install {} --force --path=/var/www/html
For premium plugins or custom themes, delete the directories and upload clean zip packages directly from source repositories.
5. Identifying and Removing Database Backdoors
Hackers often inject malicious administrator accounts, script tags, and spam content directly into your database.
Cleaning Injected Post Content
The Japanese Keyword Hack and Pharma Hack inject thousands of spam pages and redirect links into post content fields.
-- Search for script tags and obfuscated code in post content
SELECT ID, post_title, post_date FROM wp_posts
WHERE post_content LIKE '%<script%' OR post_content LIKE '%eval(%';
-- Search options table for suspicious options or auto-load scripts
SELECT option_name, option_value FROM wp_options
WHERE option_name LIKE '%hack%' OR option_value LIKE '%base64_decode%';
Deleting Unauthorized Administrators
Identify and remove newly registered admin accounts:
-- List all administrators registered recently
SELECT ID, user_login, user_email, user_registered
FROM wp_users
WHERE ID IN (
SELECT user_id FROM wp_usermeta
WHERE meta_key = 'wp_capabilities' AND meta_value LIKE '%administrator%'
);
-- Delete admin user with ID 99 (verify the ID first)
DELETE FROM wp_users WHERE ID = 99;
DELETE FROM wp_usermeta WHERE user_id = 99;
6. Removing Injected Cron and Configuration Backdoors
Malware often schedules cron jobs to re-infect clean files. This means that even if you replace all system files, the site may be re-infected within hours.
Auditing WordPress Cron Tasks
All scheduled tasks are stored as serialized arrays in the wp_options table under the option name cron. Use this WP-CLI command to inspect active scheduled events:
# List all scheduled cron events
wp cron event list --path=/var/www/html
Programmatically Sanitizing Cron Events
If you identify an unknown cron hook (e.g., wp_update_system_cache referencing a dynamic code block), use this PHP script to delete it:
declare(strict_types=1);
namespace WPPoland\Security\Cron;
/**
* Removes malicious cron hooks from the schedule.
*/
function remove_malicious_cron_event(): void {
$target_hook = 'wp_update_system_cache';
// Clear the hook if it is scheduled
$timestamp = wp_next_scheduled( $target_hook );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, $target_hook );
error_log( sprintf( 'Security Action: Unscheduled backdoor hook "%s".', $target_hook ) );
}
}
add_action( 'init', __NAMESPACE__ . '\\remove_malicious_cron_event' );
Analyzing and Disabling Persistent PHP User.ini Backdoors
A common persistence mechanism is the injection of .user.ini or .htaccess configuration file overrides in the site root directory.
Attackers define PHP runtime directives like auto_prepend_file to execute a malware script before any core WordPress code loads:
; Malicious .user.ini backdoor
auto_prepend_file = "/var/www/html/wp-content/uploads/2026/06/backdoor.jpg"
Because this file executes globally, even running empty WordPress plugins or requesting static login screens triggers the backdoor code. Ensure you search your site root for .user.ini and .htaccess file modifications and verify that no unauthorized scripts are prepended or appended.
7. Hardening Your WordPress Architecture
Once your site is clean, apply these security configurations to prevent future attacks:
1. Configure File Permissions
Set restrictive file permissions across your directory structure. The webserver user (www-data) should not have write permissions to root files:
# Set directories to 755
find /var/www/html/ -type d -exec chmod 755 {} \;
# Set files to 644
find /var/www/html/ -type f -exec chmod 644 {} \;
# Set wp-config.php to read-only for owner (400)
chmod 400 /var/www/html/wp-config.php
Changing Filesystem Ownership
In addition to permissions, ensure correct file ownership. The webserver process (e.g., www-data or apache) should not own the WordPress PHP files. Instead, set the owner to a separate deployer user (e.g., wp-user), and configure the group to www-data. This prevents a compromised web server process from writing to or modifying any .php files inside the system structure:
# Change owner to deployer user and group to web server
chown -R wp-user:www-data /var/www/html/
# Allow web server write access only to uploads folder
chown -R www-data:www-data /var/www/html/wp-content/uploads/
2. Disallow File Edits in wp-config.php
Add these directives to your wp-config.php file to disable the built-in theme/plugin editor and block installation of new plugins:
// Disable the built-in file editor
define( 'DISALLOW_FILE_EDIT', true );
// Block plugin and theme installations or updates via admin dashboard
define( 'DISALLOW_FILE_MODS', true );
// Force secure HTTPS connections for administrative logins
define( 'FORCE_SSL_ADMIN', true );
3. Rotate WordPress Salts
Rotate your authentication salts to invalidate all active user sessions and cookies. This automatically logs out any attackers:
# Shuffle authentication salts securely
wp config shuffle-salts --path=/var/www/html
4. Implementing an Immutable Container Read-Only Filesystem
For advanced setups, deploy WordPress using containerized environments (like Docker). Configure your container execution environment to mount all core directories (/wp-admin/, /wp-includes/, and root PHP files) as read-only volumes:
# docker-compose.yml example snippet
services:
wordpress:
image: wordpress:latest
read_only: true # Hardens the filesystem container against write attempts
volumes:
- ./wp-content/uploads:/var/www/html/wp-content/uploads:rw # Only uploads folder is writable
- ./wp-config.php:/var/www/html/wp-config.php:ro
By sealing the execution layer, even if an attacker discovers an LFI vulnerability, they cannot write script payloads to system directories, shielding the codebase from persistent infection.
8. Action Plan: A 90-Day Security Hardening Roadmap
Maintain a secure platform with this 90-day checklist:
- Days 1–30 (Immediate Triage): Execute a comprehensive file integrity audit using WP-CLI to detect modified core files. Rotate all database passwords, database root credentials, and SSH/SFTP access keys. Rotate the authentication salts in
wp-config.phpto terminate active sessions, and disable direct theme/plugin file editing by addingDISALLOW_FILE_EDITto your configuration file. - Days 31–60 (Forensics & Firewalls): Audit active WordPress cron tasks to verify that no malicious scheduled functions persist. Analyze webserver access logs for anomalous behavior, POST spikes to XML-RPC, or LFI execution patterns. Implement a server-level Web Application Firewall (WAF) or use a reverse proxy (e.g., Cloudflare) to block SQLi and XSS payloads at the edge.
- Days 61–90 (Hardening & Auditing): Configure automated offsite backups to a secure, write-once-read-many (WORM) storage bucket. Conduct static code analysis and penetration testing on custom themes and proprietary plugins. Schedule quarterly automated security scanning runs and establish an incident response protocol for your operations team.
Need help cleaning a compromised site or auditing your platform security? Our WordPress security team can identify entry points, clean files, and secure your database. Contact us to discuss your project requirements.







