advanced-cache.php
Loaded very early when WP_CACHE is defined as true in wp-config.php. A WP_CACHE line with no caching plugin installed is a reason to look at the file.
Guide · Drop-in backdoors
WordPress loads a handful of specially named files straight from wp-content — advanced-cache.php, object-cache.php, db.php and a few others — before any plugin runs. Caching and database plugins use them legitimately, which is exactly why a backdoor planted under one of those names gets left alone. This guide covers which drop-ins exist, when each one loads, how to tell one written by a real plugin from a loader, and how to remove a rogue one without breaking the site.
Published Last reviewed
How drop-ins work
A drop-in is a file with a reserved name, placed directly in wp-content, that WordPress loads by itself to replace or extend part of its own behaviour. There is no activation step and no setting in the admin area. If the file exists — and, for advanced-cache.php, if the WP_CACHE constant is true — it runs on every request, before must-use plugins, ordinary plugins and the theme.
The legitimate uses are common. Page-caching plugins write advanced-cache.php so a cached page can be served before most of WordPress loads; object-cache plugins write object-cache.php to connect WordPress to Redis or Memcached; database plugins, and debugging tools such as Query Monitor, use db.php. That familiarity is the attraction: a file called object-cache.php reads as infrastructure, and most people who find one assume a plugin put it there.
Drop-ins do not appear in the ordinary list of plugins. WordPress shows a separate Drop-ins tab on the Plugins screen when any exist, which most owners never open. And because a drop-in loads before every security plugin, a loader there runs before anything that might notice it.
Diagram · Where drop-ins and must-use plugins sit
Drop-ins live loose in wp-content, by exact name. Nothing activates them.
wp-content/in the order WordPress reaches each part on a request
advanced-cache.phpDrop-in: loaded first when WP_CACHE is true in wp-config.phpdb.phpDrop-in: replaces the database class whenever the file existsobject-cache.phpDrop-in: replaces the object cache whenever the file existsindex.phpThe empty “Silence is golden” file, not a drop-inmu-plugins/Must-use plugins: every PHP file directly inside loads, no activationplugins/Ordinary plugins: only those in the active_plugins option loadthemes/The active theme’s functions.php loads after all of the aboveuploads/Media; PHP here is never loaded by WordPress itselfThe full list
Only these exact names, directly in wp-content, are treated as drop-ins. A file such as object-cache-old.php is not loaded unless something includes it.
Figure 1 · The drop-ins, and what makes one suspicious
Every drop-in has a legitimate owner. A loader under the same name has none.
| Drop-in | Loads when | Normally written by | Suspicious if |
|---|---|---|---|
| advanced-cache.php | WP_CACHE is true | A page-caching plugin | No caching plugin installed |
| object-cache.php | The file exists | An object-cache plugin or host | A few lines, encoded strings |
| db.php | The file exists | A database plugin, Query Monitor | Includes a file outside a plugin |
| maintenance.php | Maintenance mode | A custom maintenance page | Any code beyond a message |
| sunrise.php | Multisite, SUNRISE set | Domain-mapping code | Present on a single site |
Loaded very early when WP_CACHE is defined as true in wp-config.php. A WP_CACHE line with no caching plugin installed is a reason to look at the file.
Loaded whenever the file exists, to replace WordPress’s object cache. No constant is needed, so the file alone is enough.
Loaded whenever the file exists, to replace the database class. Legitimately written by database plugins and by Query Monitor, which installs it as a symbolic link to its own folder.
Custom pages shown during maintenance mode, a database connection failure, or a fatal error. They should contain a message and little else.
Replaces WordPress’s fatal error handler. Rarely used legitimately; any one you did not install deserves a close look.
Multisite only. sunrise.php loads when SUNRISE is defined, usually for domain mapping; blog-deleted.php, blog-inactive.php and blog-suspended.php are status pages. On a single site they should not exist.
Where to look
Work from the WordPress root over SSH or a file manager with hidden files shown. Every check here only reads.
With full timestamps, so a file written during the incident stands out from the rest:
ls -la --time-style=full-iso wp-content/*.php
index.php is normal. Anything else is a drop-in candidate; note its owner, size and modification time.
advanced-cache.php only loads when WP_CACHE is true:
grep -n 'WP_CACHE' wp-config.php
WP_CACHE true with no page-caching plugin installed means something set it — and a caching plugin you removed long ago can leave both the constant and the file behind.
A drop-in written by a plugin names that plugin in its opening comment and usually loads code from the plugin’s own folder:
head -n 25 wp-content/advanced-cache.php wp-content/object-cache.php wp-content/db.php 2>/dev/null
Check that the plugin it names is actually installed in wp-content/plugins. A drop-in that names no plugin, or a plugin that is not there, has no owner.
Encoding, indirect execution and request parameters that should never appear in a cache or database layer:
grep -nE 'eval[[:space:]]*\(|base64_decode|gzinflate|str_rot13|assert[[:space:]]*\(|create_function|\$_(POST|GET|REQUEST|COOKIE)' wp-content/*.php
A caching drop-in may legitimately read cookies to decide whether to serve a cached page. Encoded strings and eval have no such explanation.
Query Monitor’s db.php, and some caching setups, are symbolic links into a plugin folder rather than copies. ls -la shows the target after an arrow; a link into a plugin you can name is explainable, a link anywhere else is not.
Real or rogue
A legitimate drop-in identifies the plugin that wrote it, and that plugin is installed and configured. Its settings screen usually reports the drop-in’s status, and offers to rewrite it.
Caching and database drop-ins are hundreds or thousands of lines of readable PHP. A file of a few lines, or one long line of encoded text, is not doing caching.
Includes that point into the owning plugin’s folder are normal. Includes of files in uploads, cache folders, hidden files, or paths written as escape sequences are not.
A drop-in written when the caching plugin was installed or updated is plausible. One written inside the incident window, on a site where nobody touched caching, is part of the incident.
Removing it
WordPress falls back to its own behaviour when an object-cache.php or advanced-cache.php disappears, so removing a rogue one rarely breaks anything. db.php is the exception worth checking first.
Copy every drop-in you are about to change, with its timestamps, outside the web root, and hash it:
mkdir -p ~/incident
cp -p wp-content/*.php ~/incident/
sha256sum ~/incident/*.php
The modification times bound the window everything else in the account gets checked against.
Delete the drop-in that no installed plugin owns. If it was advanced-cache.php and no page-caching plugin is installed, also set WP_CACHE back to false, or remove the line, in wp-config.php.
If the name belongs to a caching or database plugin you do use but the content was replaced, delete the file and let the plugin regenerate it from its own settings screen, or deactivate and reactivate the plugin — never edit the drop-in back by hand.
A db.php from a multi-database plugin is load-bearing: removing it can take the site offline if the setup depends on it. Confirm what the plugin expects before deleting a db.php that turns out to be legitimate.
Something had write access to wp-content. Look for the other layers below, then rotate the control panel, SFTP, database and WordPress administrator credentials, and the keys and salts in wp-config.php.
List wp-content/*.php again and compare against the preserved copy. A drop-in that reappears means a persistence layer is still writing it.
What else to check
A drop-in is one way to run code early without being activated. The same infection usually has others, and any one of them can write the drop-in back. In the ClickFix incident documented on this site, the chain lived in four layers at once: obfuscated PHP files, base64 content in the options table, the active theme’s functions.php, and a .user.ini startup directive.
Every PHP file directly in wp-content/mu-plugins loads without activation, just after the drop-ins.
An escaped @include at the top of index.php or wp-config.php is the same idea one level up.
auto_prepend_file in .user.ini runs a file before WordPress, and before any drop-in.
A WP-Cron event or system cron job that re-fetches the payload will rewrite a deleted drop-in.
Common questions
Not necessarily. Object-cache plugins and many hosts write this file to connect WordPress to Redis or Memcached. Read its header: a legitimate one names the plugin or host that wrote it and is substantial, readable code. If it names nothing, is a few lines long, or contains encoded strings or eval, treat it as a loader.
A drop-in loads before every ordinary plugin, so it has already run by the time a security plugin starts. Scanners that do read wp-content may also treat drop-ins as expected files, because so many legitimate plugins write them.
Usually not. Without advanced-cache.php, WordPress serves pages uncached; without object-cache.php, it uses its built-in in-memory cache. If a legitimate caching plugin needs the file, it can regenerate it. db.php is the one to check first, because some multi-database setups depend on it.
On the Plugins screen, a Drop-ins tab appears when any drop-in exists. WP-CLI lists them with wp plugin list --status=dropin. Both load WordPress — and therefore the drop-ins — so read the files from the filesystem first.
Drop-ins are loaded from the content directory, which wp-config.php can move by defining WP_CONTENT_DIR. Check for that constant: if it is set, the drop-ins live wherever it points.
Something else is still running and writing it: a must-use plugin, an include in a core file, an auto_prepend_file directive, a database loader, a scheduled task, or credentials the attacker still holds. Remove the layers together, then rotate access.