Must-Use Plugins and Drop-ins

WordPress loads PHP from two places that never appear on the Plugins screen and cannot be switched off from the admin. An inventory of both, read from disk.
What this check and mySites.guru tool looks at on your site
There are two places WordPress loads PHP from without ever showing it on the Plugins screen, and neither can be switched off from the admin.
Must-use plugins
Every top-level PHP file in wp-content/mu-plugins/ is loaded on every request, always, with no Deactivate link. WordPress does not look inside subdirectories there, so a folder on its own runs nothing unless there is a loader file beside it.
Drop-ins
A short list of filenames WordPress loads by name straight out of wp-content/: object-cache.php, advanced-cache.php, db.php, db-error.php, install.php, maintenance.php, php-error.php, fatal-error-handler.php, sunrise.php and the three multisite ones. Some of them run only when a matching setting is switched on, which mySites.guru checks and shows per file.
Both are read by listing the directory rather than by asking WordPress what is there. On a site that has been broken into, whatever is loaded is already running inside the request that asks the question, so a list produced by WordPress is a list the attacker can edit. A directory listing cannot be filtered.
Having these is normal, which is why the badge is neutral
Managed WordPress hosts install must-use plugins as standard, and caching plugins install drop-ins on purpose. Colouring “this site has must-use plugins” as a problem would light up a very large share of perfectly healthy sites and teach everybody to ignore the row. So the count here is inventory. Only a row that can actually be justified, something hidden, or a file the malware scanner has already flagged, is coloured.
sunrise.php gets special handling: it only runs on a multisite network and only when a setting in wp-config.php switches it on. The tool reports whether the file is present and whether it is actually being loaded, because a leftover copy on a single-site install does nothing at all. WP_CACHE is read the same way, as a mechanism rather than guessed from a filename, because the constant being true with the drop-in absent is inert.
Why a must-use plugin is the most durable place to hide code
A must-use plugin is loaded before the ordinary plugins, runs on the front end and the admin alike, and the Plugins screen offers no way to turn it off. Deactivating every plugin on the site, the standard first move when something is wrong, changes nothing about it.
Drop-ins are the same idea with a shorter list of names. object-cache.php and advanced-cache.php are what your caching plugin installs, and they are loaded extremely early. fatal-error-handler.php is the one worth knowing about: WordPress loads it whenever the site hits a fatal error, which makes it a quiet place to keep code and a hard one to notice.
On the compromise this check was built from, the loader was a two megabyte must-use plugin and three drop-ins were hijacked alongside it, with a line appended to wp-config.php purely to force WordPress to load one of them. None of it appeared in any plugin list, because none of it is a plugin as far as WordPress is concerned.
None of this goes into your plugin inventory in mySites.guru, on purpose. Reporting a drop-in as an installed plugin would put it in your update list and your vulnerability checks, where it does not belong, and it would make a planted must-use plugin look like ordinary installed software.
Every forced-load file named
You can name every line in both lists: your host’s own must-use plugin, your caching plugin’s drop-in, your security plugin’s loader. Nothing in either location begins with a dot, and nothing in either is a file you cannot account for.
How to fix it
- Read the list once, properly. Once you know what belongs on a site, you will know straight away when something new turns up.
- Name every line. If you cannot account for one, that is the one to investigate.
- Anything hidden was hidden on purpose. A file or folder whose name begins with a dot is being kept out of ordinary listings, and there is no good reason for one to be here.
- Do not delete your host’s files. Managed hosts use must-use plugins for real work, and removing one can break caching, backups or the control panel integration. Ask your host before removing anything you did not install.
- Check this alongside your scheduled events. When malware plants a must-use plugin, it usually plants a scheduled event to put it back.
What mySites.guru does about it
On every snapshot, twice a day, mySites.guru lists both locations from disk and shows you what is loaded, how big it is and when it changed. Every path links straight into the web file manager at the folder rather than at the file, so you see it among its siblings, which is what you want when three of five entries are hidden dot-files from one campaign.
No viewer was built into this page on purpose. The file manager already has the connector jail, the CSRF token, the audit trail and an editor, and a second reader here would be a second path to the same files with none of it.
This check reports rather than accusing. A steady non-zero count is fine and trends as fine; what is worth your attention is an increase.
Must-Use Plugins and Drop-ins
mySites.guru checks every connected site for this automatically and flags it the moment it appears. These run twice a day on every connected site.
Further Reading
- Five New Checks for WordPress Hacks a File Scan Cannot See - why a planted must-use plugin and three hijacked drop-ins were invisible to every file-level check on a compromised site
- Must-use plugins in the WordPress documentation - why a file in that folder loads before every normal plugin and cannot be disabled except by deleting it
- _get_dropins() in the WordPress code reference - the authoritative list of every drop-in WordPress will load by name, since no prose documentation page for them exists
