Your WordPress site is slow because one layer is doing the damage, and you probably have not found which one yet. The speed fixes people reach for first — an image plugin, a caching plugin, a theme swap — often target a layer that is not the problem. Measure, fix the slowest layer, change one thing, re-measure.
I run two sites on shared hosting and one on a VPS. The most useful thing I have learned is that a single number tells you which half of the problem to look at.
The short version
- Measure time to first byte before you install anything. It is the one number that separates server-side slowness from front-end weight.
- If TTFB is over a second on a page that is already cached, the problem is hosting, PHP or the database. No front-end trick will fix it.
- Page caching is the biggest cheap win — and the reason a site can be fast for visitors and slow for you.
- Images are usually why a page is heavy, not why it is slow to start.
- Change one thing, re-measure, then change the next. Two at once and you learn nothing.
Measure first, with one command
TTFB is how long your server takes to start answering. Run this three times against your homepage:
curl -o /dev/null -s -w "ttfb=%{time_starttransfer}s total=%{time_total}s\n" https://yourdomain.com/
On shared hosting, a cached page should answer in well under a second. A figure above 1.5 seconds on a page that is already cached means the server is struggling. Then run the same command against a static file on the same host. If the static file is fast and the homepage is slow, WordPress is doing the work. If both are slow, it is the server or the network, and the rest of this article can wait.
Two conditions: log out first, because your own admin session bypasses page caching and will always look slower; and test from elsewhere, since your ISP’s route to the host is not your visitor’s route. How to test your hosting speed properly covers that part.
Layer one: hosting and PHP
If TTFB is high on a cached page, this is where the time is going.
Check the PHP version first: Tools → Site Health → Info → Server. Anything on PHP 7.4 or older is both unsupported and slower than 8.x, and changing it is a dropdown in most control panels. Test on a staging copy if you have one, since an old plugin can break on the newer branch — but that move is worth the risk on its own.
Then look at what your plan actually allows. Shared hosting limits CPU seconds, memory and disk I/O, and when you cross a limit the host throttles you instead of showing an error. A site that is fine at 9am and crawling at 3pm is usually being throttled, not attacked. Check for resource-limit emails and a CPU usage graph in the panel — cheap hosting versus good hosting is about what those limits mean in practice.
If PHP and the limits look sane, ask support two things: whether OPcache is on, and whether the database is local.
Layer two: page caching
A page cache stores the finished HTML so PHP does not rebuild it for every visitor. Done once and properly, it removes most of the load. Done three times, it causes bugs.
- Use one layer. If your host has a built-in cache — LiteSpeed hosts, most managed WordPress plans — turn it on and skip the plugin. Stacking a host cache, a plugin cache and Cloudflare’s HTML cache is how people end up with stale pages and random logouts.
- Exclude what has to stay dynamic:
/wp-admin,/wp-login.php, and any cart or checkout page. A rule that bypasses the cache when the logged-in cookie is present is what keeps the admin usable. - Expect asymmetry. A cached site is fast for visitors and still slow for you while logged in. That is the cache working, not failing.
- Clear it after every change, or you will spend an hour debugging a fix that is already live.
If Cloudflare sits in front of the site, its default static-file cache is the safe setting for WordPress — setting up Cloudflare without breaking a small site covers where that line is.
Layer three: images and front-end weight
Images rarely cause a slow TTFB, but they cause most of the wait after the first byte and they are the easiest layer to fix.
Open the homepage and check total page weight and the largest image. A hero exported at 4000 pixels wide for a slot that renders at 1200 is the most common problem I find on small sites. Resize to the size the theme actually displays and export as WebP or AVIF.
WordPress adds loading="lazy" to images by default since 5.5, which is right everywhere except the image at the top of the page. That one should load immediately — delaying it makes the page feel slower even when the measured numbers improve. Most optimisation plugins let you exclude the first image.
Then fonts and scripts. Every web font is a separate download that blocks text from painting, so two families with four weights is four files before any content appears. If the theme loads a font you cannot see on the page, remove it. Render-blocking CSS and JS are the other half, and the honest fix is usually fewer plugins.
Layer four: plugins and the database
Every active plugin is code that runs on every request, whether or not the page uses it. The reliable way to find the expensive ones:
- Install Query Monitor on a staging copy and open a normal page. It shows the query count, the slowest queries and which plugin caused each one.
- Deactivate plugins in halves on staging, measuring TTFB after each round. You will find the culprit in a few rounds without taking the live site down.
On the database side, the measurement worth taking is autoloaded options — the rows WordPress loads on every single request. In phpMyAdmin:
SELECT SUM(LENGTH(option_value)) AS autoload_bytes
FROM wp_options WHERE autoload = 'yes';
A few hundred kilobytes is normal. If that number is measured in megabytes, a plugin is stuffing data into the options table and every page load pays for it. Expired transients are the usual offender. Clearing expired transients is safe; deleting rows you do not recognise is not.
Last piece of this layer: WP-Cron. It is triggered by page loads, so on a low-traffic site jobs run late, or several at once on the first visit after a quiet spell — exactly when a visitor notices. A real system cron job on the host is a two-line change.
Symptom, layer, first thing to try
| What you see | Layer | Do this first |
|---|---|---|
| Fast for visitors, slow for me | Page cache | Log out and re-test — nothing is broken |
| Slow at all hours, static files fast | PHP and plugins | Move to PHP 8.x, then audit plugins |
| Slow only at busy times | Host throttling | Check resource-limit emails and CPU graph |
| Quick start, page still drags | Front-end weight | Resize the hero image, cut one font |
| Random multi-second stalls | Cron or an external call | Query Monitor, then a real system cron |
The order I actually work in
Speed work goes wrong when it starts at the bottom of this list. Top to bottom, stopping at the first real improvement:
- Measure TTFB, logged out, three times, and against a static file as a control.
- PHP version and host limits — cheapest possible fix, biggest possible jump.
- One page cache, with the admin and login paths excluded.
- Images and fonts — dimensions before compression, and never lazy-load the top image.
- Plugin audit on staging with Query Monitor in hand.
- Database hygiene — autoloaded options, expired transients, a real cron.
Now the part that saves the most time: write the before number down. Every change feels faster, especially one you spent an hour on. A number on a note is the only thing that tells you whether the caching plugin did anything at all.
Today, do one thing: run that curl command against your homepage three times, logged out, and write the three TTFB numbers down. Under a second, and your next hour belongs to images and fonts. Over one and a half seconds on a cached page, and it belongs to hosting and PHP — no plugin in the directory will change that.









