Expanded and up-to-date version of this topic: Clean a hacked WordPress site: malware removal, backups and hardening.
“This site may be hacked” or “Deceptive site ahead.”
Encountering a bright red Google Safe Browsing interstitial or receiving a suspension notice from your web hosting provider is every website owner’s worst nightmare. A security compromise in WordPress is not merely a cosmetic embarrassment. It threatens customer confidential data, destroys years of accumulated search engine equity through cloaked SEO spam, and risks permanent infrastructure blacklisting across global security registries.
The vast majority of attacks targeting WordPress are entirely automated. As noted in the official WordPress Hardening Documentation, opportunistic botnets continuously scan the public internet for outdated extensions, weak credentials, and unpatched theme vulnerabilities. These automated systems do not target your brand personally; they seek unhardened servers to enlist into distributed proxy networks, spam delivery rings, and credential harvesting schemes.
This operational guide details the professional, server-level malware removal methodology required to fully disinfect a compromised WordPress installation. Automated security plugins frequently mask symptoms without eliminating underlying backdoors. True remediation requires command-line precision using SSH, WP-CLI, and direct database sanitisation.
If your primary symptom consists of foreign language product pages appearing in Google Search Console, review our detailed guide on the Japanese keyword hack before executing deletions.
Symptoms of a compromised WordPress site and initial incident triage
A security breach manifests across multiple vectors. The official documentation in FAQ: My site was hacked outlines concrete Indicators of Compromise (IoC) that confirm an active intrusion:
- Search engines like Google and Bing flag the domain on their public malware registries.
- Your web hosting provider suspends the account due to malicious resource utilization or outbound attack attempts.
- Desktop antiviruses or browser security protections block customer visits.
- Unfamiliar administrative user accounts appear in the dashboard.
- Unexplained redirects route incoming mobile traffic or search visitors to fraudulent portals.
- The site exhibits visible visual alterations, broken layouts, or unauthorized pop-up elements.
The mandatory first step is formal documentation. Prior to deleting or altering a single file, document everything observed: the exact timestamp of detection (including time zone), recent configuration changes (new plugins, theme adjustments, widget modifications), and environment specifications (PHP version, web server architecture, active SSH credentials). This forensic log serves as the foundation for incident remediation and saves critical hours whether you resolve the incident internally or engage external technical specialists.
Two preliminary checks must never be skipped. First, perform a full anti-malware scan on your local administrative workstation. A local keylogger or credential-stealing Trojan targeting your FTP or /wp-admin credentials renders server-side cleaning useless. Second, inform your hosting provider. On shared or multi-tenant servers, an infection can cross local account boundaries, and a site dispatching bulk spam can result in the entire server IP being blacklisted on global mail registries.
Taking a forensic backup before modifying files
Before touching a single script, generate a comprehensive backup of the current infected state. If an overzealous script deletion accidentally purges a modified functions.php file containing critical business logic alongside the malware snippet, you will need a fallback archive for surgical extraction.
From an SSH terminal session inside the WordPress root directory, execute:
# Archive all filesystem assets into a compressed tarball
tar -czf backup-infected-files_$(date +%F).tar.gz .
# Export the raw database dump
wp db export backup-infected-db_$(date +%F).sql --allow-rootStore these forensic archives locally or within an isolated quarantine bucket for retrospective analysis.
How to scan WordPress files for malware signatures using grep
Malware authors obscure their payloads using dynamic execution wrappers such as eval(), base64_decode(), and gzinflate(). Google Security documentation emphasizes searching for these exact functions alongside unescape() to uncover hidden script execution routines.
Execute the following pattern-matching routines from your web root to locate suspicious strings across all project files:
Common malware signatures and patterns
Locating calls to
eval()(virtually never utilized in modern, standards-compliant application code):grep -rl --include=*.php 'eval(' .Locating unauthorized script injections and hidden iframe overlays:
grep -rl --include=*.php '<script' . grep -rl --include=*.php '<iframe' .Locating encoded and obfuscated string transformations:
grep -rl --include=*.php 'base64_decode' . grep -rl --include=*.php 'str_rot13' . grep -rl --include=*.php 'gzinflate' .Locating client-side unescape wrappers in JavaScript assets:
grep -rl --include=*.js 'unescape(' .
Avoid passing the -w (word boundary) flag to grep when searching these patterns. The -w parameter demands non-alphanumeric trailing boundaries, causing searches for eval( to completely miss obfuscated chains like eval(base64_decode(.
Distinguishing false alarms from active backdoors
Not every flagged occurrence indicates malicious intent:
- Certain complex page builders or framework libraries may legitimate utilize
base64_decodefor serialized configuration transfers or SVG data URIs. - Pay strict attention to PHP scripts situated inside asset directories. Under standard WordPress architecture, the
/wp-content/uploads/directory must contain zero PHP files.
To expose all executable scripts hiding inside your uploads directory, run:
find ./wp-content/uploads -name "*.php"Treat every single PHP file returned by this query as an active compromise until conclusively proven otherwise. Legitimate extensions store assets, documents, and media in this folder, never executable server-side scripts.
Inspecting compromised sites through Googlebot cloaking simulation
Advanced compromises frequently implement user-agent or referer cloaking: they serve clean HTML to direct administrative visitors while injecting spam links and malicious redirects exclusively for Googlebot or search engine referrals. Evaluating the site via a standard browser fails to reveal the compromise.
Google Security guidelines caution against visiting infected sites in desktop browsers to avoid browser exploit chains. Instead, utilize curl from a terminal to simulate a Googlebot crawler arriving via a Google search referral:
curl -sv --referer "https://www.google.com/" \
--user-agent "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://example.com/ -o simulated-crawl.html
grep -iE 'eval|base64_decode|unescape|<iframe' simulated-crawl.htmlRepeat the request without the referer header and compare the HTTP status codes. A 301 or 302 redirect appearing strictly when the Google referer is present confirms active cloaking rules embedded in .htaccess or early PHP runtime hooks.
Replacing WordPress core files safely using WP-CLI
Attempting to surgically cleanse hundreds of core files in wp-admin and wp-includes is inefficient and prone to error. The definitive, reliable strategy is replacing all core system files with pristine originals.
WordPress documentation mandates reinstalling the identical version currently operating on the site to avoid fatal schema or hook mismatches. Using WP-CLI, download a fresh copy while preserving user content:
# Force-download pristine core files matching the active version
wp core download --version=$(wp core version --allow-root) --force --skip-content --allow-rootThe --skip-content parameter is vital: it guarantees that your /wp-content/ directory, housing media, custom themes, and extensions, remains untouched.
Keep in mind an important limitation: standard installers overwrite existing matching filenames, but they do not automatically delete newly injected alien files. An unauthorized file like wp-includes/class-wp-session.php will survive a basic --force download. To expose these rogue scripts, execute the checksum verification detailed below.
If WP-CLI is unavailable in your hosting environment:
- Download a clean archive of the identical WordPress version from WordPress.org.
- Extract the package locally.
- Delete the
/wp-content/directory andwp-config-sample.phpfrom the extracted archive. - On your web server, delete the existing
wp-adminandwp-includesdirectories entirely, upload the clean replacements via SFTP, and overwrite the root core files.
Verifying core and plugin integrity with checksum validation
WP-CLI provides built-in utilities to compare local files against official MD5 checksums generated by WordPress.org. The command wp core verify-checksums executes on the before_wp_load hook, meaning it inspects file integrity without executing potentially compromised WordPress code:
wp core verify-checksums --allow-rootOn a healthy installation, the command returns:
Success: WordPress installation verifies against checksums.If modifications or alien files exist, the diagnostic output flags them explicitly:
Warning: File doesn't verify against checksum: wp-includes/version.php
Warning: File should not exist: wp-includes/extraneous-script.php
Error: WordPress installation doesn't verify against checksums.The warning “File should not exist” is the primary indicator of an embedded backdoor. Passing the --include-root flag verifies the entire root directory (excluding expected exceptions like wp-config.php, .htaccess, and .maintenance). For non-English installations, pass the corresponding locale flag (such as --locale=pl_PL) to prevent translated resource files from triggering false warnings.
A corresponding verification exists for extensions via wp plugin verify-checksums:
wp plugin verify-checksums --all --allow-rootThis scans every installed plugin against WordPress.org repository hashes, identifying modified files (“Checksum does not match”) and rogue injections (“File was added”). Commercial or bespoke private plugins lacking public repository listings are skipped with a warning; their integrity must be restored by downloading fresh archives directly from official vendors.
Cleaning malicious files from the wp-content directory
The /wp-content/ hierarchy houses the vast majority of persistent malware footholds.
Disinfecting themes and plugin directories
For all free plugins sourced from the WordPress.org repository, the most reliable practice is complete reinstallation:
# Force reinstall a clean package directly from the repository
wp plugin install contact-form-7 --force --allow-rootFor premium plugins, obtain clean distribution archives from vendor portals and overwrite the directory assets over secure SFTP.
Examine your active theme. If you utilize a child theme, manually audit functions.php, header.php, index.php, and footer.php. Attackers frequently inject silent obfuscated code blocks at the very top or bottom of these templates to ensure execution on every page render.
Purging rogue PHP scripts from the uploads folder
Because user upload directories should exclusively hold media files, you can purge executable PHP scripts directly:
# Review the list of rogue files first
find ./wp-content/uploads -name "*.php"
# Once reviewed, delete all executable PHP files from uploads
find ./wp-content/uploads -name "*.php" -deleteAlways inspect the output list before executing -delete to confirm no custom caching or third-party extension has misplaced legitimate configuration files.
Purging database tables of spam injections and unauthorised users
Malware persistence frequently extends beyond files into database tables:
Rogue administrative users: Access the database CLI or phpMyAdmin and inspect user tables:
SELECT * FROM wp_users;Identify accounts you do not recognize, particularly those registered with temporary domains or arbitrary character sequences. Remove them immediately.
Spam injections in post content: Doorway page injections insert thousands of spam posts containing counterfeit advertisements or pharmaceutical spam. Query the
wp_poststable for suspicious signatures:SELECT ID, post_title FROM wp_posts WHERE post_content LIKE '%<script%'; SELECT ID, post_title FROM wp_posts WHERE post_content LIKE '%eval(%';
If your installation suffered a Japanese Keyword Hack, thousands of auto-generated spam posts must be purged from the database, followed by sitemap resubmissions in Google Search Console to expedite deindexing.
Auditing administrative accounts with wp user list
Querying wp_users displays account records but obscures capability roles stored in wp_usermeta. The command wp user list aggregates both tables, filtering directly by role:
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered --allow-rootCross-examine the returned roster against authorized personnel. The user_registered timestamp helps correlate unauthorized account creations with the incident timeline. On Multisite networks, verify network super-administrators as well:
wp super-admin list --allow-rootIf an attacker compromised your primary administrator account and locked you out, access the database directly, update the user email in wp_users to an address you control, and trigger the standard password reset routine from the login interface.
Inspecting wp-config.php and htaccess configuration files
Attackers frequently modify configuration files to establish persistent backdoor loaders:
wp-config.php: Open the file and review every line. Attackers often insert hidden includes loading temporary payloads:
include '/tmp/sess_f3a987...'; // SUSPICIOUS MALWARE LOADERCompare your configuration with
wp-config-sample.php. It should contain only database connection parameters, table prefix definitions, authentication salts, and explicit system constants..htaccess: Attackers append rewrite rules designed to intercept traffic from search engine referers while displaying normal content to direct visitors. If unauthorized directives exist, regenerate the standard WordPress rewrite structure:
# BEGIN WordPress <IfModule mod_rewrite.c> RewriteEngine On RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteBase / RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> # END WordPress
Locate all auxiliary .htaccess files distributed across nested directories:
find . -name ".htaccess"Immediate hardening steps after malware eradication
Once file and database sanitisation is complete, enforce immediate preventive hardening:
Rotate all authentication credentials:
- Reset passwords across every administrative and user account.
- Update database passwords and mirror the change inside
wp-config.php. - Rotate hosting panel, SSH, and SFTP credentials.
Regenerate cryptographic salt keys: Regenerating salts invalidates all active session cookies, instantly expelling any logged-in attackers:
wp config shuffle-saltsDisable dashboard file editing: Append to
wp-config.php:define( 'DISALLOW_FILE_EDIT', true );This prevents any attacker who might obtain administrative credentials from modifying theme or plugin code directly from the administrative dashboard.
Restrict XML-RPC access: Unless required by external publishing apps or Jetpack, disable XML-RPC to eliminate brute-force amplification vectors. On Apache 2.4, insert into
.htaccess:<Files xmlrpc.php> Require all denied </Files>
Restoring filesystem permissions and restricting database accounts
The official WordPress Hardening Guide specifies standard operational permissions: directories set to 755, files set to 644, owned by the dedicated web user account:
find /path/to/wordpress/ -type d -exec chmod 755 {} \;
find /path/to/wordpress/ -type f -exec chmod 644 {} \;The configuration file wp-config.php should be restricted to 400 or 440, making it readable only by the owner and the web server daemon.
Under routine operation, the application database account only requires Data Manipulation Language privileges (SELECT, INSERT, UPDATE, DELETE). If multiple websites reside on the same server, guarantee that each site operates on an isolated database with an independent database user account, preventing lateral cross-site compromise.
Finally, analyze server access logs. Web server access logs provide forensic clarity on how the initial compromise occurred, detailing IP addresses, timestamps, and specific request URIs associated with file inclusion attempts or brute-force requests.
Verifying total site disinfection over subsequent monitoring cycles
Remediation is only confirmed when the site consistently passes repeated integrity audits over subsequent days. Re-run your grep pattern scans, execute find ./wp-content/uploads -name "*.php", and run wp core verify-checksums periodically.
Re-audit database tables using wp user list --role=administrator to ensure no rogue administrators reappear. If an indicator fails or malware resurfaces, do not merely repeat the cleaning steps. Identify the initial entry vector, such as an unpatched extension, an insecure FTP password, or an unhardened server configuration, and close the vulnerability before repeating remediation.
Reviewing Search Console security reports and requesting review
If search engines flagged your website, public warnings remain visible until formal verification succeeds. The Security Issues Report inside Google Search Console serves as the definitive reference.
The report categorizes security incidents into hacked content, malware, and social engineering. Sample URLs illustrate affected paths. Address every single flagged issue across the entire property; partial fixes will not restore search visibility.
Once all issues are resolved and verified, submit a review request via the Request Review button. An effective review submission accomplishes three tasks:
- Clearly explains the root cause of the compromise identified during analysis.
- Outlines the precise remediation steps executed across files, databases, and server configurations.
- Documents post-cleanup verification evidence proving the property is secure.
Review evaluations typically take from several days to several weeks. Avoid submitting multiple review requests while a decision is pending, as premature requests can introduce review delays.
Strategic conclusions for sustained site integrity
Defending WordPress against malicious software is an ongoing engineering discipline rather than a one-off task. Lasting protection relies on proven digital hygiene:
- Deploying timely updates across WordPress core, plugins, and parent themes.
- Prohibiting pirated or nulled themes and plugins, which serve as primary malware distribution channels.
- Storing regular, verified backups in isolated off-site cloud storage.
If technical remediation feels daunting or your site experiences recurring compromises, professional assistance is recommended. An incomplete cleanup leaves hidden backdoors that allow threat actors to regain persistence within days.
Review our comprehensive WordPress security audit services to identify structural vulnerabilities and protect your digital infrastructure.







