June 12, 2026
What a self-healing WordPress backdoor actually looks like
You paid someone to clean your hacked WordPress site. They did. The spam posts disappeared, the warnings went away, and you went back to running your business. A week later — sometimes a day later — it's all back. New spam posts, same redirects, same Google Safe Browsing warning. The contractor swears they got everything.
They didn't. What you're looking at is a self-healing backdoor, and it's one of the most frustrating things a small-business owner can deal with because it makes you feel like you're losing your mind. This post explains exactly how these things work, why most cleanups miss them, and what it actually takes to kill one.
The basic pattern: payload, backup, rebuild
A self-healing backdoor isn't one piece of malware. It's a system with three parts working together:
- The payload. This is the visible thing — the file or code that does the actual damage. It injects spam links, redirects visitors to a sketchy site, creates fake admin users, or posts garbage content to your blog. This is what scanners find and what cleanups remove.
- The backup. A second copy of the payload, hidden somewhere the cleanup crew won't look. It's not running. It's just sitting there, waiting.
- The rebuild trigger. A scheduled task — usually a WordPress cron job — that periodically checks whether the payload still exists. If it doesn't, the trigger pulls the backup out of storage, writes it back to disk (sometimes to a new filename), and the infection is live again.
You delete the file. The cron job fires a few hours later. The file is back. From your perspective the site is reinfecting itself out of nowhere. From the attacker's perspective it's working exactly as designed.
The whole point of this architecture is to survive a normal cleanup. Generic scanners and contractors who charge $99 to "remove malware" are looking at files. The persistence logic isn't in a file you'd recognize as malicious. It's split across the database and a plugin that looks completely legitimate.
Where the pieces actually hide
Once you understand the pattern, the question becomes: where does each piece live? WordPress gives attackers a lot of options.
Must-use plugins (mu-plugins). This is the dirtiest one. The wp-content/mu-plugins/ directory auto-loads every PHP file inside it, and most site owners don't even know it exists. It doesn't show up in the normal Plugins page in wp-admin. A file dropped here runs on every request, no activation needed, no admin notice. I've seen backdoors install themselves here with names like wp-cache.php or index.php that blend in perfectly with what a legitimate caching plugin might create.
Modified core files in wp-includes. WordPress core files should be byte-identical to the official release. Attackers know nobody actually checks. A few extra lines tacked onto wp-includes/load.php or wp-includes/functions.php will run on every page load and never trigger a plugin update. The file's modification date might be changed to match other core files so it doesn't look fresh.
Database-resident options. This is where it gets nasty. The wp_options table is supposed to hold site settings — your site title, your admin email, plugin configurations. Attackers stuff base64-encoded PHP payloads into option rows with innocent names. A small loader script reads the option, decodes it, and eval()s the result. Delete the file? The DB option is still there. Reinstall the plugin? The loader was never in the plugin to begin with.
Scheduled tasks (WP-Cron). WordPress has its own cron system that runs tasks based on entries in the cron option in wp_options. Attackers register a custom hook that fires every hour or every six hours. The hook checks the payload file, and if it's missing or modified, rewrites it. You can see scheduled tasks with the WP-CLI command:
wp cron event list
If you don't recognize a hook name, that's a red flag. Legitimate WordPress and plugin hooks have predictable names like wp_scheduled_delete or woocommerce_cleanup_sessions. Random strings or hooks tied to plugins you don't have installed are suspicious.
Hijacked legitimate plugins. The cleverest version. An attacker modifies a single function inside a plugin you actually use and depend on — something like Contact Form 7 or Yoast SEO. The modification is small, maybe ten lines, buried inside a 200-line file. The plugin still works perfectly. Your scanner sees a "known good" plugin and skips it. Updating the plugin overwrites the modification, but the attacker's code already used its first run to plant copies elsewhere, so the next cron cycle restores everything.
Why most scanners miss it
Generic malware scanners — including the free tiers of the popular WordPress security plugins — work by matching files against a signature database. They look at PHP files, hash them, check the hashes against known-bad patterns, and flag what matches.
This approach catches obvious infections. It also misses self-healing backdoors for three reasons:
The rebuild logic isn't in a file. It's in the database. Scanners that don't audit wp_options and the cron schedule have no way to see it. Even scanners that do look at the database often only flag known-bad strings, and a clever attacker base64-encodes or even just rot13s their payload to dodge string matches.
The persistence lives in "trusted" code. If the loader is six lines tacked onto a legitimate plugin file, signature scanners ignore it because the file as a whole still looks like the real plugin. The malicious lines are too short and too generic to match a unique signature.
The payload changes every time. When the cron job rebuilds the payload, it can write to a new filename, change variable names, re-encode the body. Even if a scanner flagged version one, version two looks different enough to slip through.
This is why a contractor can scan a site, get a clean report, declare victory, and have the infection back within 48 hours. They removed what the scanner found. The scanner didn't find the system that rebuilds it.
Detection: what to actually look for
If you suspect a self-healing infection, or if a previous cleanup didn't hold, here's what serious detection looks like.
File integrity baseline against WordPress core. Compare every file in wp-admin/ and wp-includes/ against the official release for your WordPress version. WP-CLI makes this trivial:
wp core verify-checksums
Any file that comes back as modified needs eyes on it. Core files should never be modified. If yours are, you have a problem.
Plugin checksum verification. Same idea, for plugins from the official WordPress.org repository:
wp plugin verify-checksums --all
This won't catch infections in premium plugins (they're not in the repo), but for the bulk of your plugins it will surface tampering immediately.
wp_options audit. Pull the full list of autoloaded options, sort by size, and look at the biggest ones. Legitimate options are usually small. A 200KB autoloaded option named something like widget_recent_entries is almost certainly a payload.
wp option list --autoload=on --fields=option_name,size_bytes --format=csv | sort -t, -k2 -n -r | head -20
Anything anomalous — large, oddly named, or with base64/eval-looking contents when you inspect it — gets investigated.
Cron audit. List every scheduled event and account for each one. If a hook doesn't tie back to a plugin you have installed and recognize, treat it as hostile until proven otherwise.
Outbound traffic patterns. Self-healing malware often phones home or pulls payloads from a command server. If you have access to server logs or a firewall that logs outbound connections, look for traffic to domains that don't belong — your site shouldn't be making HTTP requests to random Russian or Indonesian hosts.
mu-plugins directory. Just go look. Most sites should have nothing in wp-content/mu-plugins/ or only files installed by hosting providers (some managed WP hosts drop their own there). Anything else needs justification.
A real case: 115 spam posts that wouldn't die
I cleaned up a Southern California contractor's WordPress site after a previous contractor's "cleanup" failed twice. By the time it got to me, the site had been infected for weeks. Symptoms: more than 115 spam blog posts — Japanese-language SEO spam, mostly — appearing on a schedule. The prior contractor would delete them all, scan the site, declare it clean. Within a couple of days the posts were back, sometimes the exact same titles.
The visible damage was wp_posts entries getting created automatically. That's not something an attacker types in by hand 115 times. There was code doing it, and that code was surviving cleanup.
[TODO: Sebastian — name the specific mechanism here. Was it a mu-plugin? A modified theme functions.php? A wp_options-resident loader plus a cron event that called wp_insert_post()? Describe the actual rebuild chain you found.]
Once the root mechanism was removed — not just the spam posts, but the system creating them — the site stayed clean. I rebuilt the site's file integrity baseline, replaced WordPress core, audited every plugin, locked down wp-config.php, restricted PHP execution in wp-content/uploads, and put proper SPF/DKIM/DMARC in place so the site's domain couldn't be used for spam either. Full writeup is in the case study if you want the longer version.
The lesson from that engagement is the same lesson behind this entire post: if you only remove what you can see, you're treating symptoms. The infection is a system. You have to find the whole system.
What a real cleanup actually involves
If you're hiring someone to clean a WordPress infection, here's what should be on their checklist. If they can't speak to most of these, get someone else.
- WordPress core checksum verification and replacement of any modified files
- Plugin and theme verification, including manual inspection of premium plugins that aren't in the public repo
- Full database audit, specifically
wp_options(autoloaded entries) andwp_users(rogue admin accounts) - Cron event audit with every hook accounted for
- mu-plugins inspection
- Review of
.htaccess,wp-config.php, and any PHP files inwp-content/uploads - Credential rotation: database password, WordPress salts, all admin passwords, hosting panel password, SFTP keys
- Post-cleanup hardening so the same vulnerability can't be re-exploited
- A re-check 7 and 14 days after cleanup to confirm nothing came back
That last point matters. A real cleanup includes a follow-up. If the site re-infects, you need to know within days, not when a customer calls to ask why your site is showing them slot machine ads.
When to call for help
If your site has been cleaned once and the infection came back, stop paying the same person to do it again. The mechanism survived their process. Doing it harder won't help — you need someone who treats it as an incident, not a scan-and-delete job.
If you're seeing any of these signs, your site likely has a persistent backdoor:
- Spam posts, users, or files reappearing after deletion
- Search Console showing pages you didn't create
- Visitors reporting redirects you can't reproduce when logged in
- Modified core or plugin files you didn't change
- Outbound connections from your site to domains you don't recognize
These don't go away on their own. They get worse, because every day the backdoor is live, the attacker can swap in new payloads, sell access to other criminals, or use your domain in phishing campaigns that wreck your reputation with Google and your email recipients.
If you want the full story of the Southern California contractor engagement — the timeline, what failed in the prior cleanup, what I actually did to kill it — read the case study. And if your WordPress site is in this situation right now, the malware removal and incident response service is built specifically for it: full forensic cleanup, root-cause identification, hardening, and a 14-day re-check so you know it's actually dead.