How to Detect a WordPress Backdoor
Diagnosis

How to Detect a WordPress Backdoor

A backdoor is the part of a hack that survives your cleanup. The visible malware — the redirects, the spam pages, the injected ads — gets removed and the site looks fine. Two weeks later it's all back, because a single small file you didn't touch let the attacker walk right in again. This page shows you how to find that file (or files), what to look for in each location, and how long the hunt realistically takes.

What a WordPress backdoor actually is

A backdoor is a piece of code an attacker leaves behind specifically to maintain access after the initial break-in. It's not the same as malware. Malware is what attackers do with the access (redirect your visitors, send spam, mine crypto). The backdoor is how they keep getting back in to install or refresh that malware.

Backdoors are designed to look harmless. They're often small — sometimes 30 lines of PHP, sometimes a single line of base64-encoded code. They live in places nobody routinely audits: the uploads folder, must-use plugins, theme function files, database options. They survive plugin updates, password changes, and most automated cleanups because nothing visible changes.

If your site has been compromised once and you didn't specifically hunt for backdoors during cleanup, assume one is still there. Statistically, attackers leave between two and five backdoors per compromise — the redundancy is intentional. Finding one is good. Stopping after one is how reinfection happens.

The 6 places backdoors hide

1. Inside `/wp-content/uploads/`. Uploads is for images, PDFs, and documents. WordPress never executes PHP from here. So any `.php` file in this folder, no matter how innocent the name, is a backdoor. Common disguises: `wp-includes.php`, `class-config.php`, `image.php`, anything that sounds vaguely WordPress-y.

2. Modified WordPress core files. Attackers append a few lines to legitimate files like `wp-load.php`, `wp-blog-header.php`, or `index.php`. The file works exactly as before, but the appended code runs on every page load. The malicious snippet is usually base64-encoded so it doesn't look like obvious code at a glance.

3. The `wp-content/mu-plugins/` directory. Must-use plugins load automatically and don't appear in the WordPress admin's plugin list. If you've never opened this directory via SSH or your file manager, you've never seen what's in it. Most legitimate WordPress sites have either no mu-plugins or a small handful added by managed hosts.

4. Theme `functions.php` files — including inactive themes. Active themes get reviewed; inactive ones almost never do. Attackers add backdoors to inactive themes specifically because admins delete the active theme during cleanup but leave the dormant ones alone. The file is still readable by PHP and a clever backdoor can include itself from there.

5. `wp_options` rows in the database. The options table is loaded on every WordPress page request. Attackers add a row with serialised PHP code that gets unserialised and executed by certain plugins. The option name is usually random or designed to look like a transient cache key.

6. Scheduled cron jobs. WordPress has its own scheduler (`wp_cron`); your hosting also runs system cron. A malicious scheduled task can re-download the visible malware on a timer — which is why some sites "get hacked again" exactly 24 hours after each cleanup.

Manual detection — file system

These checks need SSH access to your server. Most managed and shared hosts include SSH (SiteGround, Kinsta, WP Engine, Cloudways, DreamHost, any VPS). Check your hosting control panel for "SSH Access" or "Terminal" before starting.

Check 1 — PHP files in uploads: `find wp-content/uploads -name "*.php"`. Should return zero results on a clean site. Anything returned is almost certainly a backdoor.

Check 2 — modified core files: `wp core verify-checksums` (requires WP-CLI). Compares every WordPress core file against the official checksums for your version. Anything flagged has been modified.

Check 3 — modified plugin files: `wp plugin verify-checksums --all`. Same idea, applied to every installed plugin. Custom plugins (built in-house, not from the repository) can't be verified, but everything from the official repo can.

Check 4 — mu-plugins directory: `ls -la wp-content/mu-plugins/` and read every file. If you didn't put it there and your host didn't put it there (check their docs), it's suspicious.

Check 5 — recently modified files: `find . -type f -mtime -30 -name "*.php" -not -path "./wp-content/cache/*"`. Lists every PHP file modified in the last 30 days. The list should match the changes you actually made.

Check 6 — all themes' functions.php: `find wp-content/themes -name functions.php`. Open every one (including inactive themes), and look for unfamiliar `eval(`, `base64_decode(`, `gzinflate(`, or `assert(` calls. These are the standard backdoor primitives.

Manual detection — database

Run these via your hosting's phpMyAdmin or `wp db query` from SSH.

Check 7 — wp_options malicious entries: `SELECT option_name FROM wp_options WHERE option_value LIKE '%base64_decode%' OR option_value LIKE '%eval(%' OR option_value LIKE '%gzinflate%';`. Returns any option storing executable code — almost always malicious.

Check 8 — wp_postmeta injection: `SELECT post_id, meta_key FROM wp_postmeta WHERE meta_value LIKE '%<script%' OR meta_value LIKE '%base64_decode%';`. Catches malware injected into post meta fields, a common attack vector for WooCommerce stores.

Check 9 — injected scripts in posts: `SELECT ID, post_title FROM wp_posts WHERE post_content LIKE '%<iframe%' OR post_content LIKE '%<script src="http%';`. Returns posts containing iframes or external scripts. Some are legitimate (embeds you added) — review each result.

Check 10 — rogue admin accounts: `wp user list --role=administrator`. Verify every account is one you created. Common attacker usernames: `wpadmin`, `support`, `admin2`, or your own first name with a small variation.

Check 11 — orphan tables: `SHOW TABLES LIKE 'wp_%';`. Compare against the standard WordPress tables. Tables you don't recognise — especially with random-looking names — were planted.

Check 12 — wp_cron events: `wp cron event list`. Every hook should belong to WordPress core, your installed plugins, or your theme. Anything else is a scheduled re-installer.

How long this takes

Realistic time estimate for a thorough manual hunt:

Best case — 30 to 45 minutes. You know exactly which check is going to flag, you find the backdoor on Check 1 or 2, you remove it, you stop. Rare unless you've done this before.

Typical case — 2 to 4 hours. You walk through every file-system check, find one backdoor, remove it, then walk through the database checks to confirm nothing else is hiding. This is what most thorough cleanups look like.

Worst case — 8 to 12 hours. You go through all 12 checks, find multiple backdoors in different locations, have to research each one to understand what it does before removing it, then re-verify everything. Common when the original compromise is more than a few weeks old.

Add another 1 to 3 hours if you've never used SSH or WP-CLI before — the learning curve is part of the cost.

If your time is worth $50/hour, the typical case is $100 to $200 of your time. The worst case is closer to $400 to $600. The toolkit (below) runs the same 16 checks in 2 to 3 minutes per site, which is most of why it exists.

After you find a backdoor

Don't just delete and move on. The backdoor is a symptom; the entry point that let the attacker in originally is still open until you find and patch it.

Step 1 — contain. Take a forensic snapshot first: copy the backdoor file (or database row) to a safe location for review. Don't immediately delete — you need to know what it did.

Step 2 — read the code. Even base64-encoded backdoors are decodable. Drop the encoded string into an online base64 decoder (offline tools exist if you'd rather not paste it to a public site). The decoded payload tells you what the backdoor was doing — uploading files, executing arbitrary code, modifying admin accounts. This tells you what else to check.

Step 3 — remove it. Delete the file or DELETE the database row. Don't comment it out — delete completely.

Step 4 — find every copy. Backdoors come in groups. After you find and remove one, run all 12 checks again. Then a third time the next day, in case the attacker had a re-installer scheduled.

Step 5 — patch the entry point. Common entry points: outdated plugin with a known vulnerability, weak admin password, file upload form without proper validation, exposed XML-RPC. The most common single source is an outdated vulnerable plugin — update everything, and use a scanner to confirm nothing exploitable is left exposed.

Related fix guides

GuardingWP checks your site for the 11 most common WordPress vulnerabilities — plus scans your installed plugins against the known CVE database. Free, no account required.

Scan your site for the entry points attackers exploit →

Skip the manual `find` commands

Hunting for a backdoor by hand means hours of grep, find, and diff against a clean WordPress install. The Forensic Toolkit automates the full backdoor sweep: checksums every core file, flags PHP files in suspicious locations (uploads, mu-plugins, themes/twenty*), and dumps modified-recently lists — in 30 seconds via one bash command on your server.

Get the Forensic Toolkit — from $25 →

Prefer to have this handled for you? Get this fixed — Full Hardening ($149)