Replacing plugins and themes removes only two code locations. Persistence can live in must-use plugins, uploads, wp-config.php, root bootstrap files, database options, scheduled tasks or hosting configuration. Stolen credentials can also recreate files without any backdoor code.
Stop cycling the same cleanup and expand the trust boundary.
Prove what "returns" means
Record the exact filename, hash, database value, redirect or unknown user that reappears, plus time after cleanup. Compare it with the preserved original.
Do not assume similar names mean the same payload. Cache or a restored backup can also make old content appear.
Correlate recurrence with page requests, cron, deployments and logins.
Check must-use and drop-in code
Inspect wp-content/mu-plugins and drop-ins such as advanced cache/database/object-cache files. They can load before normal plugins and remain active even when the Plugins screen looks clean.
Compare known vendor files with trusted packages and document custom ownership. Do not execute obfuscated code to identify it.
Review cache configuration before replacing legitimate drop-ins.
Inspect writable content directories
Search uploads, cache, language, upgrade and backup directories for executable files and unexpected server rules. PHP execution should normally be restricted in upload areas where the site’s functionality permits it.
Preserve suspicious samples, then remove or isolate them through a clean rebuild. Do not delete all media because one file is malicious.
Check old and date-based upload folders.
Check cache and PHP process state
Opcode cache, object cache, CDN cache and long-running PHP workers can temporarily serve or execute old state after files change. Record cache layers and process restarts before interpreting one recurrence.
Purge/restart only after evidence is preserved and the source is corrected. Cache cannot normally rewrite a deleted disk file by itself, so a recreated file still needs a writer.
Compare disk hash with served response to avoid confusing stale output with reinfection.
Review root and configuration
Compare WordPress core/root files with the matching official release and inspect wp-config.php, .htaccess, PHP configuration and auto-prepend values.
An include pointing outside the document root can reload malware after every code replacement. Check parent directories owned by the hosting account.
Preserve ownership, permissions and modification metadata before correction.
Inspect database persistence
Review options, widgets, posts/templates, users/usermeta and scheduled event data for injected code or loaders. Page builders and tag settings can output JavaScript without file changes.
Work from a database snapshot and use precise changes. Avoid broad replacements that damage serialized data or legitimate content.
Check multisite network options separately.
Review scheduled execution
Inspect WordPress cron, Action Scheduler, server cron and hosting-panel tasks. Record unknown hook, command, owner and schedule before disabling.
A malicious task may recreate a deleted file minutes later. Legitimate WooCommerce, backup and certificate jobs must not be removed blindly.
Also check deployment and restore automations.
Inspect external loaders and webhooks
Review tag manager, CDN workers, external JavaScript, webhook destinations and remote management agents. A clean local filesystem can still produce malicious browser behaviour through an authorised-but-compromised external account.
Secure each provider, revoke unknown tokens/users and compare deployed versions. Do not remove legitimate analytics code without identifying its owner and rollback.
Revoke account persistence
Audit WordPress administrators/application passwords/sessions, hosting users/keys, database access, primary email, DNS/CDN and source-control tokens. Rotate from a clean device.
An attacker using SFTP can restore a file after every scan. File cleanup cannot revoke that credential.
Use separate site-level access and MFA.
Rebuild across the confirmed boundary
Create a clean root, install trusted components, validate custom code and import assessed database/uploads. Close the initial vulnerability and isolate the infected tree.
Test recurrence triggers: cron cycle, cache warm-up, administrator login, form and checkout. Monitor file/database/account changes after reopening.
Request emergency persistence analysis when the same indicator returns after replacement. Share hashes, paths and redacted timelines—never payloads, credentials or customer databases publicly.