Guide · Must-use plugin loaders

A file in mu-plugins runs on every request, and you never switched it on.

Must-use plugins are PHP files that WordPress loads from wp-content/mu-plugins on every request, before ordinary plugins, with no way to deactivate them from the admin area. Hosts and agencies use them legitimately. An attacker uses them because a file there survives a plugin cleanup, does not appear in the normal plugin list, and can hide itself — or a rogue administrator — from the screens you would check. This guide covers how to read the folder directly, tell a host’s files from a loader, and remove one in an order that holds.

Symptom
A file in wp-content/mu-plugins nobody can account for, or a Must-Use tab you never noticed
Mechanism
WordPress loads every PHP file directly in the folder, with no activation
Visibility
Only on the Must-Use tab — which the plugin itself can hide
Complication
Many managed hosts install their own must-use plugins

How must-use plugins work

Why mu-plugins is a favourite place for a loader

WordPress reads the must-use plugin directory — wp-content/mu-plugins unless wp-config.php moves it with WPMU_PLUGIN_DIR — and loads every PHP file directly inside it, in alphabetical order, on every request. There is no activation and no deactivation: if the file is there, it runs. Must-use plugins load after the drop-ins and before every ordinary plugin and the theme.

Only files directly in the folder load. A PHP file in a subfolder of mu-plugins runs only if a file at the top level includes it — which is exactly how legitimate must-use setups load larger code, and how a loader can keep its payload one level down where a quick look misses it.

The admin area shows must-use plugins only on a separate Must-Use tab of the Plugins screen, and a must-use plugin can hide that tab entirely with a single filter. The same code can hook the user query so a rogue administrator does not appear on the Users screen, or remove itself from the plugin lists. None of that changes what is on disk.

Where to look

How to read the must-use folder without running it

Work over SSH or a file manager with hidden files shown. Every check here only reads. WP-CLI commands that load WordPress also run every must-use plugin, so read the filesystem first.

Figure 1 · Reading the folder, not the screen

Two files the host can explain, one written last week by the site’s own user.

acct@site:~/public_html
ls -la --time-style=+%F wp-content/mu-plugins
-rw-r--r-- 1 acct acct  1840 2024-03-11 host-cache-purge.php
-rw-r--r-- 1 acct acct   920 2024-03-11 host-staging.php
-rw-r--r-- 1 acct acct  4417 2026-10-12 wp-qa7k2.php
grep -cE 'base64_decode|eval|show_advanced_plugins' wp-content/mu-plugins/*.php
wp-content/mu-plugins/host-cache-purge.php:0
wp-content/mu-plugins/host-staging.php:0
wp-content/mu-plugins/wp-qa7k2.php:3
The newest file has no header, a random name, and the filter that hides the Must-Use tab. File names, owners and dates are illustrative. Illustrative reconstruction.
  1. 01

    Check whether the folder has been moved

    wp-config.php can point WordPress at a different must-use directory:

    shell
    grep -nE 'WPMU_PLUGIN_DIR|WPMU_PLUGIN_URL|WP_CONTENT_DIR' wp-config.php

    If either constant is set, the folder that loads is wherever it points — check that location as well as the default.

  2. 02

    List the files that load

    Every PHP file directly in the folder, with full timestamps:

    shell
    find wp-content/mu-plugins -maxdepth 1 -type f -name '*.php' -exec ls -la --time-style=full-iso {} +

    Each of these runs on every request. Note the owner, size and modification time of every one.

  3. 03

    List what sits in subfolders

    Files below the top level, which load only if a top-level file includes them:

    shell
    find wp-content/mu-plugins -mindepth 2 -type f -ls

    A subfolder that belongs to a host or agency tool usually has a top-level loader named after it. A subfolder no top-level file mentions, or hidden files with a leading dot, need explaining.

  4. 04

    Read each file’s header

    A legitimate must-use plugin names itself, and usually its author, in a Plugin Name comment:

    shell
    head -n 20 wp-content/mu-plugins/*.php

    A file with no header still loads; WordPress simply lists it under its filename. No header, a random name, or one long line of code is a reason to read the whole file.

  5. 05

    Search for loader and hiding patterns

    Encoding, indirect execution, and the filters a loader uses to hide itself or an account:

    shell
    grep -nE 'eval[[:space:]]*\(|base64_decode|gzinflate|str_rot13|assert[[:space:]]*\(|create_function|show_advanced_plugins|all_plugins|pre_user_query' wp-content/mu-plugins/*.php

    show_advanced_plugins hides the Must-Use tab; all_plugins edits the plugin list; pre_user_query can drop accounts from the Users screen. Legitimate code uses these rarely, and never alongside encoded strings.

Host or attacker

How to tell your host’s must-use plugins from a loader

Many managed WordPress hosts install their own must-use plugins for caching, staging, updates, or security, and agencies use them for site-wide settings. A must-use plugin is not suspicious in itself.

It belongs to something you can name

A host’s file usually carries the host’s name in its header or folder, and the host’s documentation or support can confirm it. Ask before removing anything you cannot place.

It is readable code

Legitimate must-use plugins are ordinary PHP with comments and functions. A file that is mostly an encoded string, or builds code from fragments before running it, has no innocent explanation.

Its date fits its story

A host’s file appears when the site was set up or migrated and changes when the host updates it. One written inside the incident window, by the site’s own user, is part of the incident.

It does not hide anything

A host has no reason to hide the Must-Use tab or an administrator account. Code that does either is concealing something.

Removing it

The order that makes the removal hold

A loader in mu-plugins is often the piece that reinstalls everything else, so remove it before cleaning the layers it feeds — after preserving it.

  1. 01

    Preserve the folder first

    Copy the whole must-use directory, with timestamps, outside the web root, and hash every file:

    shell
    mkdir -p ~/incident
    cp -pr wp-content/mu-plugins ~/incident/
    find ~/incident/mu-plugins -type f -exec sha256sum {} +

    The modification times bound the window everything else in the account gets checked against.

  2. 02

    Remove the rogue files

    Delete the top-level loader and anything in a subfolder it included. Leave files your host or agency has confirmed; if you are unsure about one, move it outside the web root rather than deleting it.

  3. 03

    Look at the Users screen again

    With the loader gone, any administrator it was hiding becomes visible. Record and remove accounts nobody can vouch for, and check application passwords on the remaining administrators.

  4. 04

    Find what wrote it, then rotate

    Something had write access to wp-content. Check the other layers below, then rotate the control panel, SFTP, database and WordPress credentials, and the keys and salts in wp-config.php.

  5. 05

    Re-check after a few days

    List the folder again and compare it with the preserved copy. A file that reappears means another persistence layer is still writing it.

What else to check

Other places code runs without being activated

A must-use loader is one of several ways to run code on every request without a plugin being switched on, and an infection that uses one usually uses more. In the ClickFix incident documented on this site, the chain lived in obfuscated PHP files, base64 content in the options table, the active theme’s functions.php, and a .user.ini startup directive at the same time.

Drop-ins

advanced-cache.php, object-cache.php and db.php in wp-content load even earlier, under names that look like infrastructure.

Core-file includes

An escaped @include at the top of index.php or wp-config.php runs a hidden payload before WordPress itself.

The options table

Encoded loader content stored in options survives every file replacement.

Fake plugins

A plugin folder named like a real plugin, or a modified hello.php, is the activated counterpart of a must-use loader.

Common questions

Common questions about mu-plugins malware

I have a mu-plugins folder I never created. Is my site hacked?

Not necessarily. Many managed hosts, migration tools and agencies create it and install their own must-use plugins. Read each file’s header and ask your host about anything you cannot place. A file with no header, encoded content, or code that hides plugins or users is the warning sign — not the folder itself.

How do I deactivate a must-use plugin?

You cannot from the admin area; must-use plugins have no activation state. Removing or moving the file out of the folder is the only way to stop it loading. Preserve a copy first.

The Must-Use tab is not on my Plugins screen. Does that mean the folder is empty?

No. The tab appears only when must-use plugins exist, and a must-use plugin can hide it with a filter. List the folder from the filesystem to be sure.

Do files in subfolders of mu-plugins load?

Not by themselves. WordPress loads only the PHP files directly in the folder. A file in a subfolder runs when a top-level file includes it, which is normal for larger legitimate must-use plugins and a common way for a loader to keep its payload out of sight.

Does wp plugin list show must-use plugins?

Yes, with --status=must-use. But it loads WordPress, which runs every must-use plugin first — including the one you are investigating, which is then in a position to interfere with what you see. Read the folder from the filesystem first and treat that as the record.

I removed the file and it came back. What now?

Another layer is still writing it: a drop-in, an include in a core file, an auto_prepend_file directive, a database loader, a scheduled task, or credentials the attacker still holds. The layers have to come out together.