Bali, Indonesia
Insights

Slow wp-admin on Every Page? Check Autoloaded Options in wp_options First

October 26, 2026
0:00
Every screen in wp-admin takes several seconds to load, even something as plain as Settings > General. The site gets little traffic, the server is not under load, and turning plugins off one at a time does not produce a clear culprit. When that pattern shows up, one of the first places worth checking is the wp_options table, and specifically the rows WordPress loads on every single request whether the page needs them or not. This article walks through the problem as a post-mortem: what the impact looks like, what usually gets ruled out first, where the real cause tends to appear, how to mitigate it without breaking anything, and what keeps it from coming back.
The symptom that confuses people most is the split between the front end and the dashboard. Visitors see a reasonably fast site, while the owner sees a dashboard that drags on every click. That split is usually a caching artifact. Most WordPress sites serve front-end pages from a page cache, whether that lives in a plugin, in LiteSpeed, or in Nginx. A cached page never reaches PHP, so it never touches the database. wp-admin is almost never page-cached, because it is personalized and has to reflect changes immediately. Every admin request boots WordPress from scratch: PHP loads core, plugins, and the theme, then queries the database. Any fixed cost in that boot process gets paid on every admin page, every AJAX call, and every uncached front-end request, such as a logged-in user or a cart page. On the server side, the picture is usually unremarkable. CPU is not pegged, there is free memory, and MySQL is not showing a wall of slow queries. Time to first byte on admin pages is high, but nothing obvious is on fire.
The usual sequence is reasonable, and it is worth doing in order, because each step removes a whole class of causes.
  1. Check server resources: load average, memory, swap, and disk I/O. If the server itself is starved, nothing else matters yet.
  2. Check PHP workers. If PHP-FPM or LiteSpeed is running out of workers, requests queue before they even start. The PHP-FPM slow log is useful here, and it is covered in this article on reading the PHP-FPM slow log.
  3. Deactivate plugins one at a time and switch to a default theme, watching whether admin load time changes.
The third step is where this kind of problem hides. Deactivating a plugin stops its code from running, but it does not remove the data the plugin wrote to the database. Settings, caches, logs, and license data typically stay in wp_options until the plugin is properly uninstalled, and plenty of plugins do not clean up even then. So the plugin you removed months ago may still be costing you on every request, and toggling plugins on and off changes nothing because the cost is not in any active plugin's code. That is the moment to stop looking at code and start looking at data.
wp_options is a simple key-value table. Each row has an option_name, an option_value, and an autoload flag. On every request, early in the boot process, WordPress runs one query that pulls every row marked for autoload and keeps the result in memory. The idea is sensible: options that almost every page needs, like the site URL or active plugin list, get loaded in one round trip instead of dozens. The problem is that the autoload flag is set by whoever writes the option, and the default for a long time was to autoload. So the payload grows with every plugin that stores something large and never sets autoload to off. A plugin that caches an API response, keeps an activity log, or saves a big serialized array of settings adds that whole blob to every request. PHP has to transfer it from MySQL, unserialize it, and hold it in memory, even on a page that never reads it. One detail to know before running any query: WordPress 6.6 changed the values used in the autoload column. Older installs use yes and no. Newer ones use on, off, auto, auto-on, and auto-off, and treat yes, on, auto-on, and auto as autoloaded. Including all four in the query covers both cases.
SQL
-- Total size of autoloaded options (replace wp_ with your table prefix)
SELECT COUNT(*) AS autoloaded_rows,
       ROUND(SUM(LENGTH(option_value)) / 1024) AS autoloaded_kb
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto');

-- The 20 largest autoloaded rows
SELECT option_name, LENGTH(option_value) AS bytes
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto')
ORDER BY bytes DESC
LIMIT 20;
Both queries are read-only. There is no universal correct size, but a healthy site typically lands in the hundreds of kilobytes or less. Recent WordPress versions also include a Site Health check that warns when the autoloaded total crosses a threshold, so Tools > Site Health is a quick first look if you cannot reach the database directly. The second query is where the cause usually becomes obvious. Look at the option names. Most plugins prefix their options with their own slug, so a row named after a plugin that is no longer installed is a strong candidate. Rows starting with transient or site_transient are transients, which are short-lived cached values. They live in this table when the site has no persistent object cache. A transient saved without an expiration time is autoloaded, and some plugins create them that way and never clean up. It also helps to look at the row count. Thousands of small autoloaded rows can matter just as much as a handful of huge ones.
The tempting move is to delete the biggest rows. Resist that for now. An option that looks abandoned might still be read by an active plugin under a slightly different name, and deleting it can reset settings or break a feature in a way that is hard to trace later. The safer order is: back up, switch autoload off, confirm nothing broke, and only then delete. Warning: the following commands change the database. Take the backup first and keep it until you have confirmed the site works.
BASH
# Measure the current autoloaded total (older WP-CLI releases use --autoload=yes)
wp option list --autoload=on --format=total_bytes

# Back up the options table (adjust the table name if your prefix is not wp_)
wp db export wp_options-backup.sql --tables=wp_options
Then, for each suspicious row, stop it from loading on every request without removing it. The option name below is a placeholder.
BASH
# Keep the row, but stop loading it on every request
wp option set-autoload old_plugin_cache_option off

# Remove expired transients only; this does not touch live ones
wp transient delete --expired
If a row is still in use, setting autoload to off does not break it. WordPress fetches it with a separate query the first time something asks for it, which costs one extra query on pages that need it instead of a large payload on every page. That is the trade-off you want for anything large and rarely read. After the change, rerun the size check and load a few admin pages. If the difference is clear, you have found the cause. Wait a reasonable period, check the plugin features that might have used those rows, and then delete the rows that belong to plugins that are definitely gone. If you are on an older WP-CLI that does not have set-autoload, the same change can be made with an UPDATE on the autoload column, but do that only after the backup and only for named rows, never with a broad pattern match.
Trimming the table fixes today's problem. It comes back if nothing changes about how the site accumulates data, so it is worth looking at the causes rather than just the rows. The most common source is plugin churn. Sites that have tried many plugins over the years collect leftovers from each one. Using a plugin's uninstall path, when it has one, instead of only deactivating it helps, and it is worth reading whether a plugin documents what it removes. The second source is plugins that use wp_options as general-purpose storage for logs, queues, or cached API data. If one keeps reappearing in the top-20 list after cleanup, that is a design issue with the plugin, and the practical options are to change its settings, report it to the developer, or replace it. The third is transients without a persistent object cache. Adding Redis or Memcached as an object cache moves transients out of the database, which helps. It does not shrink the autoloaded options themselves, though. WordPress caches the entire autoloaded set as one item, often called alloptions, and if that item grows beyond what the cache backend accepts, such as Memcached's per-item size limit, the cache may fail to store it and WordPress falls back to the database on every request. An object cache is a good addition, but it is not a substitute for a lean options table. Finally, make the size visible. The total_bytes check above is cheap and read-only, so it fits easily into a routine review alongside slow query logs. Watching the number over time is more useful than any single threshold, because a sudden jump points straight at whatever was installed or updated around then. For support teams, this is one of the checks that saves a long back-and-forth. When a customer reports a slow dashboard on a quiet site and plugin toggling has gone nowhere, running the two read-only queries above takes a minute and either confirms or rules out the autoload payload before anyone starts blaming the server. The short version: if every admin page is slow and no single plugin explains it, measure the autoloaded options before anything else. Find the largest rows, back up the table, switch autoload off rather than deleting, confirm nothing breaks, and then remove what is truly abandoned. After that, keep an eye on the total so the problem does not quietly rebuild itself.