A slow WordPress page is not automatically a hosting problem. Before you upgrade, split the time into three buckets: PHP/plugin execution, database query time, and network time to first byte. If query time is low and PHP time is high, a bigger database does not help. If query time is high, a faster CPU can hide a missing index for a while. Profile before you upgrade.
Step 1: Measure a stable baseline
Use a staging copy with the same plugins and data. Measure the uncached page for profiling, then measure the cached page separately.
URL="https://staging.example.com/"
for i in 1 2 3 4 5; do
curl -s -o /dev/null -w '%{time_starttransfer}' "$URL"; echo
done
Do not trust one run. Use the median of several runs. If the page is cached, the uncached number is the one that tells you about plugins and queries.
Step 2: Log page time, query count, and query time
Add this to wp-config.php while profiling:
define('SAVEQUERIES', true);
Create wp-content/mu-plugins/wp-profile.php:
<?php
add_action('shutdown', function () {
if (!defined('SAVEQUERIES') || !SAVEQUERIES) {
return;
}
global $wpdb;
$total = 0.0;
$slow = [];
foreach ((array) $wpdb->queries as $q) {
$seconds = (float) $q[1];
$total += $seconds;
if ($seconds >= 0.05) {
$slow[] = [
'sql' => $q[0],
'seconds' => round($seconds, 4),
'caller' => $q[2],
];
}
}
error_log(sprintf(
'WP_PROFILE page=%ss query=%ss queries=%d slow=%s',
timer_stop(0),
round($total, 4),
count($wpdb->queries),
wp_json_encode($slow)
));
});
Load the page, then read the PHP error log. Remove the constant and the mu-plugin after profiling. SAVEQUERIES adds overhead and should not stay on in production.
Step 3: Sweep plugins on staging
Do not do this on production. Deactivate all active plugins, then activate one at a time and measure the same URL.
cd /path/to/wordpress
measure() {
for i in 1 2 3; do
curl -s -o /dev/null -w '%{time_starttransfer}' "$1"; echo
done | sort -n | sed -n '2p'
}
wp plugin list --status=active --field=name > /tmp/active-plugins.txt
wp plugin deactivate --all
while read -r plugin; do
wp plugin activate "$plugin"
echo "$plugin $(measure https://staging.example.com/)"
wp plugin deactivate "$plugin"
done < /tmp/active-plugins.txt
wp plugin activate $(cat /tmp/active-plugins.txt)
Skip plugins that the site needs to boot. Also test with the default theme if you suspect the theme. A plugin that adds a small query can still add a large external HTTP call on every page load.
Step 4: Read the slow queries
Open the log line that starts with WP_PROFILE. Take the SQL from the slow array and run EXPLAIN in MySQL:
EXPLAIN SELECT post_id
FROM wp_postmeta
WHERE meta_key = 'example_key';
Look for:
-
type=ALLon a large table - a large
rowsestimate Using temporaryUsing filesort
Add an index only after you check the real query pattern and test write performance. An index that helps one report can hurt checkout or editorial saves.
Step 5: Check autoloaded options
Large autoloaded options are loaded on every request. Find the largest ones:
SELECT option_name, LENGTH(option_value) AS bytes
FROM wp_options
WHERE autoload = 'yes'
ORDER BY bytes DESC
LIMIT 20;
Do not delete rows blindly. Identify stale options from plugins you no longer use, then remove them through the plugin or a backup-backed cleanup.
Step 6: Check transients
Expired transients can pile up in wp_options. List the largest:
SELECT option_name, LENGTH(option_value) AS bytes
FROM wp_options
WHERE option_name LIKE '%transient%'
ORDER BY bytes DESC
LIMIT 20;
If you see many expired transients, clean them from the database or with a maintenance routine you control.
Step 7: Decide what to buy
- Query time high, page time high: fix queries, indexes, or autoloaded data before buying a bigger database.
- Query time low, page time high: profile plugins, theme hooks, external HTTP calls, and PHP workers.
- Time to first byte high before WordPress runs: look at DNS, TLS, network, and host-level caching.
- Only logged-in admin pages are slow: profile admin hooks, not the front end.
Hosting upgrades are useful when the profile shows a real resource limit. They are a poor substitute for a missing index or a plugin that calls a remote API on every request.
AumCreate is a one-person WordPress shop in Singapore. It does custom WordPress development and deployment setup. If you want help turning this profile into a fix, the services page is here: AumCreate services.
Top comments (0)