WordPress Malware Keeps Coming Back? Where It Hides

unflagdomain Team·UPDATED October 2, 2026

WordPress malware keeps coming back for one of two reasons: a hidden copy you did not remove rebuilt the files you deleted, or the hole it came in through is still open. Deleting the infected file fixes neither. This guide covers where the copies hide, the order to remove them in, and why a blocklist flag can outlast even a cleanup that finally holds.

TL;DR: If deleted files reappear within minutes, something on the server is rewriting them: a must-use plugin, a drop-in such as db.php or advanced-cache.php, an auto_prepend_file line in .user.ini, a row in the database, a scheduled task, or a segment of shared memory. In September 2026 Sucuri documented a backdoor that keeps its payload in eight such places at once, each able to rebuild the others (Sucuri, 2026). Remove the copies that are not files first, then the files in a single pass, then close the entry point. Ask security vendors to re-check the domain only after the site has stayed clean.

This is written for the owner who has already cleaned the site once, or three times, and watched the same redirect, the same spam page or the same antivirus warning return. You did not do the cleanup wrong so much as incompletely, and the part you missed is rarely where a file scanner looks.

Why does WordPress malware come back after you remove it?

Two different problems look identical from the outside, and they need different fixes.

Persistence. The attacker left more than one copy, and the copies restore each other. You delete the obvious file, and on the next page load another component writes it back. The tell is speed: the same file, with the same contents, is back within seconds or minutes.

Reinfection. Every copy really was removed, but the way in is still open: a vulnerable plugin, a reused password, a leaked SFTP credential. The tell is a gap: the site is quiet for days or weeks, and then new files appear under different names. Sucuri found that 39% of reinfected sites still had at least one out-of-date core, plugin or theme (Sucuri, 2024).

The stubborn cases are usually both at once. And both defeat the same habit, which is cleaning file by file. A file scanner compares what is on disk against what should be there. Persistence increasingly lives where there are no files to compare: in the database, in PHP's own configuration, in memory.

Where does the malware hide? Nine places a cleanup misses

Work through these in any order to find things. The order you remove them in comes later, and it matters.

  • Must-use plugins in wp-content/mu-plugins/. WordPress enables everything in this directory automatically, it cannot be disabled from wp-admin, and it does not show in the default list on the Plugins screen (WordPress developer documentation, 2026). Check with wp plugin list --status=must-use.
  • Drop-ins: wp-content/db.php, advanced-cache.php and object-cache.php. WordPress loads these very early, before ordinary plugins. Legitimate ones are put there by a caching or database plugin you installed. If you run no such plugin, the file has no reason to exist. Check with wp plugin list --status=dropin.
  • An auto_prepend_file line in .user.ini, php.ini or .htaccess. It tells PHP to run a chosen file before every script on the account, WordPress or not. PHP caches .user.ini settings, by default for 300 seconds (PHP manual, 2026), which is why the removal order below starts here.
  • The active theme's functions.php, with an injected block, often fenced between marker comments so the malware can find and refresh its own code.
  • A fake plugin in wp-content/plugins/ with a plausible name, frequently with a twin in mu-plugins. It may filter itself out of the plugin list, so compare the directories on disk against what the dashboard shows.
  • The database. A row in the options table holding a large encoded blob, plus small control options and transients. Some variants add a database trigger that recreates an administrator whenever one is deleted.
  • Scheduled tasks. WP-Cron events with random-looking names, and entries in the server's own crontab that call WordPress on a timer whether or not anyone visits.
  • A hidden administrator. An account with full privileges that is filtered out of the Users screen and out of the user counts.
  • Shared memory. On servers that support System V shared memory, the payload can sit in a memory segment. It survives the deletion of every file on disk.

Also look for ZIP archives with random hexadecimal names in wp-content, in uploads or in the theme folder. They are restore bundles: a compressed copy of the whole kit, kept for the day you clean up.

What does this look like in a real infection?

Sucuri published an analysis on 30 September 2026 of a backdoor it calls SC, after the markers it leaves in injected code. It is the clearest current example of why file-by-file cleanup loses.

SC keeps itself in eight places on disk: a .user.ini that sets auto_prepend_file; a visible shim and a hidden dot-prefixed loader in wp-content; the db.php and advanced-cache.php drop-ins; an injected block in the active theme's functions.php; and a fake plugin present twice, once in mu-plugins and once in plugins. The file names vary between sites. Off disk it keeps a full copy of the payload in an options row and, where the server allows it, in a shared-memory segment. It registers cron hooks and creates or adopts an administrator that it then hides from the user list.

Each component exists largely to write the others back. Sucuri describes the result as "a circular system with no single point you can remove to stop it" (Sucuri, 2026). Remove the plugin and a drop-in restores it; remove the drop-in and the theme restores it; remove every file and the next request rebuilds the set from the database or from memory.

Its operators do not rely on one command server that a host could block. The backdoor reads its instructions from a smart contract through roughly twenty public Ethereum gateways. What comes back can include JavaScript to inject into your pages, which on a store means checkout skimming; new PHP to install; and a list of security plugins to deactivate and delete.

Two things the analysis does not say, and this guide will not pretend otherwise: how many sites are infected, and how the attackers get in. You do not need to have SC specifically for any of this to apply. The hiding places above are shared by many families; SC simply uses all of them at once.

How do you remove it so it stays gone?

Sequence matters more than thoroughness. The steps below follow the order Sucuri recommends, with two preparations of our own in front.

Before you start. Take a full backup of files and database, infected as they are, so you have evidence and a way back. Then keep public traffic away from the site while you work, with a maintenance rule at the web server or an IP allowlist, because every request is a chance for the malware to rebuild what you just removed.

  • Neutralise the prepend first. Replace the file that auto_prepend_file points to with an empty stub, and only then remove the directive from .user.ini, php.ini and .htaccess. PHP keeps the old setting cached for up to five minutes. Delete the target while the setting is still cached and every PHP request on the account fails.
  • Clear the copies that are not files. Delete the payload row from the options table, the control options and transients, and the shared-memory segment. On shared hosting the segment may be owned by another account, in which case only the host can remove it. Ask.
  • Remove the scheduled tasks and any database triggers.
  • Remove the hidden administrator.
  • Delete the files in one pass. Loaders, shim, both copies of the fake plugin, the injected drop-ins, the block in functions.php, any restore bundles. One pass, not one file at a time with page loads in between.
  • Scan again, then watch the same paths. A clean scan a minute after cleanup proves little. Unchanged paths three days later prove a lot.

These commands surface most of it. The table prefix on your site may not be wp_.

grep -n "auto_prepend_file" .user.ini php.ini .htaccess 2>/dev/null
ls -la wp-content/ wp-content/mu-plugins/
wp plugin list --status=must-use
wp plugin list --status=dropin
wp cron event list
wp user list --role=administrator
ipcs -m
SELECT option_name, LENGTH(option_value) AS bytes
FROM wp_options ORDER BY bytes DESC LIMIT 20;

SHOW TRIGGERS;

SELECT ID, user_login, user_registered
FROM wp_users ORDER BY user_registered DESC LIMIT 10;

Compare the administrator list from the database against the one WP-CLI and the dashboard give you. An account that exists in the table but not on the screen is your hidden admin.

Then close the door, or you have only reset the clock. Update core, plugins and themes; delete, rather than deactivate, anything abandoned; change every password, including hosting, SFTP and the database user; and replace the salts in wp-config.php so that every existing session dies. Our guides to WordPress malware removal and cleaning a hacked website cover the scan-and-verify loop, and WordPress hardening covers what keeps the next attempt out. If you were running an unpatched core in late September 2026, read whether your site was hit through CVE-2026-87902 as well; a webshell left by that bug is exactly the kind of entry point that brings an infection back.

When is it faster to rebuild than to keep cleaning?

When it has come back twice after a careful pass. At that point you are losing to something you have not found, and searching longer costs more than starting clean.

A rebuild means a fresh WordPress core, with plugins and the theme downloaded again from their official sources and never copied from the infected install. Content comes across through the database, and the database is inspected before it is trusted: the options table, the users, the triggers.

Restoring a backup works only if the backup predates the infection and you close the entry point straight afterwards. Be careful with database backups in particular. If the payload lives in an options row, a database restore brings it back with everything else.

There is no shame in handing this to a cleanup professional. A mesh like SC is built specifically to beat an owner working through files in an afternoon.

How do you know it is really gone?

Not by a clean scan alone. Check all of these, a few days apart:

  • The paths you cleaned are unchanged after 72 hours of normal traffic. find wp-content -name '*.php' -newermt '2026-10-01' with your cleanup date lists anything written since.
  • Checksums match. wp core verify-checksums and wp plugin verify-checksums --all compare what is on disk against the official releases.
  • No administrator you cannot account for, and no scheduled task you cannot explain.
  • No unexplained outbound traffic. In the SC case that means requests from the server to public Ethereum gateways.
  • Visitors get clean pages. Load the site logged out, from a different network, and from a search result. Payloads that target visitors often stay hidden from a logged-in owner. Our guide to the signs your website has been hacked lists what to look for.

Why does my site stay flagged after the malware is finally gone?

Because a blocklist records what your domain served, not what is on your server today. Every week the malware kept returning, security vendors' crawlers kept seeing it. Each failed cleanup added to that record instead of clearing it.

The flags do not lapse when the files are clean. Most vendors re-check only when asked, and they re-scan the site when they review a request. That is why timing matters here more than in an ordinary hack: ask while the infection can still rebuild itself and the re-scan fails, the flag stays, and the next reviewer has a reason to look harder. Wait until the site has stayed clean for several days, then ask.

For Google the request is manual: the Security Issues report in Search Console, with no API to automate it. Every other vendor runs its own false-positive form or security inbox on its own schedule, which our guide on how long blacklist removal takes sets out honestly.

Start by finding out where you stand. Our free blocklist check looks the domain up across security vendors and shows which ones are flagging it, with no signup. It is a reputation check, not a malware scan: a clean result does not prove a clean server, and a flagged one tells you that cleanup alone will not undo what vendors already recorded.

If the list is long, that is the gap unflagdomain.com fills. We send a removal request to each flagging vendor that accepts one by email for a one-time €39: plain text, written separately for each vendor, with your own address as Reply-To so the replies come to you. Our catalog covers 133 active vendors. 104 of them take requests by email; for the ones that only take a web form, and for Google Safe Browsing, you get a ready-to-paste request and a guided step on your dashboard. The limits, stated plainly: we do not scan for or remove malware, so everything above this section is yours or your security provider's to do first. We guarantee the dispatch, never the delisting, because each vendor decides on its own. If the re-scan after payment finds zero flags, the payment is refunded automatically.

Once you are delisted, stopping the site getting blacklisted again is mostly a matter of keeping the entry point closed.

A cleanup that holds

Malware that keeps coming back is not bad luck and it is not a stronger virus. It is a second copy you have not found, or a door you have not shut. Look where scanners do not: the must-use directory, the drop-ins, PHP's own configuration, the database, the scheduler, the user table, memory. Remove what is not a file before what is, delete the files together, and watch the paths afterwards. Then, and only then, ask the vendors that flagged you to look again.

// FAQ
  • Either a second copy rebuilt them or the entry point is still open. Persistence mechanisms such as must-use plugins, drop-ins, an auto_prepend_file directive, a database row, cron jobs or shared memory rewrite deleted files, often on the next page load. If files return after days rather than minutes, the attacker is getting back in through an unpatched plugin or a stolen password.

  • Sometimes, but do not rely on it. Scanners compare files on disk, and persistent infections also live in the database, in PHP configuration and in memory, where a file scan does not look. Some backdoors, including the SC family Sucuri documented in September 2026, are able to deactivate and delete security plugins on instruction from their operators.

  • Only if the backup predates the infection and you close the entry point immediately afterwards. A files-only restore leaves a payload stored in the database untouched, and a database restore can bring an infected options row or a hidden administrator back with it. Inspect the options table, users and triggers before trusting any restored database.

  • Not by themselves. They are WordPress drop-ins, loaded early on every request, and caching or database plugins install legitimate ones. They are suspicious when you run no plugin that needs them, when they contain long encoded strings, or when they reappear after you delete them. List them with wp plugin list --status=dropin.

  • Until the site has stayed clean for several days under normal traffic. Security vendors re-scan when they review a request, so asking while the infection can still rebuild itself produces a failed review and keeps the flag in place. Confirm the cleaned paths are unchanged and checksums match, then request the review.