Why is your WordPress site slow when hosting is not the culprit
Learn more about WordPress speed optimization at WPPoland. When a WordPress site starts dragging, the instinctive reaction is to upgrade the hosting plan. While a better server helps, it often masks underlying architectural issues. Spending money on hosting before optimizing the application layer is a temporary fix for a permanent problem.
What slows WordPress down besides hosting
A faster server will perform better, but if your site is bogged down by unoptimized resources, you’re just running inefficient code on a faster CPU. Here are the most common bottlenecks that actually drain your performance:
- Plugin bloat and query debt: Each active plugin isn’t just a file; it’s a potential series of database queries and external dependencies. Many of those queries are redundant, asking for the same data on every request when it could have been cached once.
- Unmanaged image assets: High-resolution images without modern formats (like AVIF or WebP) are the #1 cause of slow LCP. A hero photo exported straight from a camera or a stock library is often several times heavier than the same image encoded as AVIF at the size it is actually displayed.
- Missing object cache: Constant database hits for static data force the server into redundant work. Site options, navigation menus and widget settings are read on every page load, even though they rarely change.
- Third-party script latency: External trackers, fonts, and embeds often block the main thread and ruin your Core Web Vitals. Web fonts loaded from a third-party host, social media tracking pixels and live chat widgets are the usual offenders.
How to read PageSpeed Insights field data for a WordPress site
PageSpeed Insights shows two different kinds of numbers on one screen, and mixing them up is a common reason a WordPress fix appears to do nothing. The field section comes from the Chrome User Experience Report (CrUX): real Chrome users who opted in, aggregated over the previous 28-day collection period and reported at the 75th percentile (PageSpeed Insights documentation). The lab section is one simulated load on a single device with fixed network conditions, so the two sets of values can differ.
Three consequences for diagnosis:
- Check which level you are looking at. When a URL does not have enough data of its own, PSI falls back to origin-level data. On a low-traffic blog post the field LCP may describe the whole domain, not that page.
- Prioritise with field data, debug with lab data. web.dev advises that when both exist, field data is what you should use to prioritise, and lab data is for debugging (web.dev: why lab and field data can differ). Lab tests load the page with a cold cache and cannot predict when a real visitor will click, which is why Total Blocking Time is only a lab proxy for INP, not a substitute. Our INP optimisation guide covers what to do once the field data points there.
- Expect a delay. Because the window is 28 days, a fix deployed today shows fully in the field numbers only about four weeks later. A lab score that improves overnight is a good sign, not proof.
How to check whether TTFB points at the server
TTFB is where the “is it the hosting” question gets a measurable answer. web.dev defines TTFB as the sum of redirect time, service worker startup, DNS lookup, connection and TLS negotiation, and the request up to the first byte of the response. It treats 0.8 seconds or less as a rough guide for good and anything above 1.8 seconds as poor (web.dev: Optimize Time to First Byte).
That breakdown matters on WordPress because not all of it is PHP. A visitor who lands on http://, is redirected to https:// and then to the www host pays for every hop before WordPress starts working. The DevTools Network panel shows each redirect as a separate request, and those belong in the server or CDN configuration, not in the database. In the field, TTFB comes from CrUX or the web-vitals JavaScript library; in the lab, from the DevTools Network panel or WebPageTest.
The 200 ms figure used elsewhere in this article is a stricter working target for a WordPress page served from a page cache. A value between 200 ms and 0.8 s is not failing by Google’s guide. It is a reason to check whether the page cache is being hit at all.
How to use Site Health to spot WordPress speed problems
WordPress has built-in checks under Tools, Site Health, and some of them are about speed. Since WordPress 6.1, Site Health tests whether a full page cache is present and whether the server response time is acceptable, with 600 ms as the default threshold that hosts can change through the site_status_good_response_time_threshold filter. It also recommends a persistent object cache once the site’s data passes thresholds that hosts can override with site_status_persistent_object_cache_thresholds (Make WordPress Core, cache checks in 6.1). Both checks run only in production, so a staging copy will not show them.
WordPress 6.6 added a check for autoloaded options: it reports an issue when the options loaded on every request add up to more than 800 KB. The same release stopped autoloading any single option larger than 150,000 bytes unless the code explicitly asks for it, a limit adjustable with the wp_max_autoloaded_option_size filter (Make WordPress Core, autoload changes in 6.6). A failing autoload check usually leads back to the orphaned plugin options described in the database section below.
How to find the slow plugin with Query Monitor
Query Monitor turns “WordPress is slow” into a named component. Its database panel flags slow, duplicate and failed queries, and groups them by query type, component or calling function, so a query that one plugin repeats on every request appears under that plugin’s name (Query Monitor on GitHub). Two other panels are easy to miss:
- HTTP API calls: every server-side request made through the WordPress HTTP API, with failed requests and non-200 responses highlighted. A plugin that contacts a remote server while the page is generated adds that round trip to TTFB.
- Hooks and actions: every hook fired during the request with its callbacks, priorities and components, which shows where a plugin attaches itself to pages where it has no visible job.
Run it on a staging copy where possible. Query Monitor shows its output to logged-in administrators, and logged-in requests usually bypass the page cache, so the numbers describe the uncached case: the work the server does for every visitor the cache misses.
How to speed up a slow WordPress site
The full optimisation workflow is in our WordPress speed optimisation guide. The steps below are the ones a diagnosis most often points to.
1. Implement selective object caching
Instead of hammering the database for common queries, use the Transients API or a persistent object cache (like Redis). This is crucial for dynamic sites where page caching isn’t always possible.
The Transients API works as a check-then-store pattern. WordPress first looks for the value under a named key. If it is there and has not expired, it comes back immediately without touching the posts table. If not, your code runs the query, stores the result with an expiry time, and returns it.
function get_performance_optimized_posts() {
// Attempt to get from cache first
$posts = get_transient('featured_posts_query');
if (false === $posts) {
$posts = new WP_Query([
'posts_per_page' => 5,
'no_found_rows' => true, // Skip overhead if pagination isn't needed
'post_status' => 'publish',
]);
// Cache for 12 hours
set_transient('featured_posts_query', $posts, 12 * HOUR_IN_SECONDS);
}
return $posts;
}The no_found_rows argument deserves attention. By default WP_Query asks MySQL to count every matching row so it can build pagination links. A “latest five posts” widget never paginates, so that count is pure overhead, and switching it off removes work from every request that runs the widget.
When to use transients and when to use Redis:
- Transients: for query results with a clear expiry time. Without a persistent object cache they are stored in the
wp_optionstable; with one installed, WordPress keeps them in Redis or Memcached instead. - Redis or Memcached: for a persistent object cache that survives between HTTP requests. It pays off on high-traffic sites, WooCommerce stores and membership sites, where many pages cannot be served from a full-page cache because the visitor is logged in or has a cart.
2. Move beyond simple compression
“Compressing” images is only half the battle. You should strive for AVIF support and responsive sizing to ensure mobile users aren’t downloading desktop-sized assets.
A layered image strategy:
- Format: serve AVIF first, WebP as the fallback, and JPEG or PNG only as the last resort.
- Dimensions: use
srcsetandsizesso the browser picks a file that matches the screen width. - Lazy loading: apply
loading="lazy"to images below the initial viewport, and never to the LCP image itself, which should load as early as possible. - Image CDN: services such as Cloudinary or imgix can resize and convert images on the fly if you would rather not generate every variant yourself.
<!-- Example of a well-formed responsive image -->
<picture>
<source type="image/avif" srcset="image-400.avif 400w, image-800.avif 800w, image-1200.avif 1200w">
<source type="image/webp" srcset="image-400.webp 400w, image-800.webp 800w, image-1200.webp 1200w">
<img src="image-800.jpg" alt="Description" loading="lazy" width="800" height="450"
srcset="image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w"
sizes="(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px">
</picture>The explicit width and height attributes matter as much as the format. They let the browser reserve space before the file arrives, which is what keeps CLS down.
3. Optimize the output buffer
If you aren’t using a dedicated caching plugin, you can implement Gzip compression at the PHP level, though doing this via .htaccess or Nginx config is usually more efficient.
// Basic compression logic if server-level config is unavailable
add_action('init', function() {
if (!ob_start("ob_gzhandler")) ob_start();
});4. Audit your plugins before anything else
A plugin audit is usually the change with the best return for the effort. Sites collect plugins over years, each installed for one campaign or one feature, and nobody checks what they cost on every request.
How to run the audit:
- Install Query Monitor: it shows how many database queries each plugin triggers, how much memory it uses and how long its hooks take on the page you are viewing.
- Find the heavy ones: sort by query count and time, and look for plugins that do a lot of work on pages where they have no visible job.
- Weigh the alternatives: for each heavy plugin, ask whether a lighter plugin does the same, or whether the feature is small enough to write as a few lines in the theme.
- Deactivate what you do not need: duplicate security plugins, form builders nobody uses, a slider plugin that serves one page.
Plugins that often cost more than they give:
- Page builders (Elementor, Divi) loading their assets on pages that were not built with them.
- Social sharing plugins that load scripts on every page.
- SEO plugins with every optional module switched on.
- Statistics plugins that write each visit to the WordPress database.
5. Clean up the database
Over time the WordPress database collects data that slows queries down:
- Post revisions: WordPress keeps a revision each time you save, so a post edited many times carries a long tail of old copies.
- Expired transients: they can stay in
wp_optionsuntil something clears them. - Orphaned options: uninstalled plugins often leave their settings behind, sometimes with
autoloadswitched on, so they are loaded on every request. - Orphaned metadata: post, user and comment meta that points to records which no longer exist.
// Limit post revisions in wp-config.php
define('WP_POST_REVISIONS', 5);
// Or disable them entirely (not recommended for sites with several editors)
define('WP_POST_REVISIONS', false);Tools for the job:
- WP-Optimize: scheduled clean-up of revisions, transients and orphaned data.
- phpMyAdmin: for manual queries when you know exactly what you are deleting.
- WP-CLI:
wp transient delete --expiredandwp db optimizefrom the command line, which is the safest option on sites with large tables.
Take a database backup before any clean-up. A wrong query against wp_postmeta is not something an undo button fixes.
6. Put a CDN in front of the site
A CDN (content delivery network) copies your static files to servers around the world, so visitors download them from a location close to them rather than from your origin server.
What a CDN gives a WordPress site:
- Lower TTFB for static assets: files come from the nearest edge location.
- Less load on the origin: images, CSS and JavaScript stop hitting your server.
- DDoS protection: most CDNs absorb attack traffic before it reaches you.
A sensible Cloudflare starting point for WordPress:
- Set SSL/TLS to “Full (strict)” so traffic is encrypted all the way to your server.
- Add cache rules for static assets, and bypass the cache for
/wp-admin/, logged-in users and WooCommerce cart and checkout pages. - Keep Brotli compression enabled.
- On paid plans, consider Polish for automatic image optimisation, or keep doing it in WordPress if you already generate AVIF.
How to monitor WordPress performance
Performance work is not a one-off task. WordPress sites keep changing with new content, plugin updates and configuration tweaks, and without monitoring the speed slowly drifts back down. For alerting and long-running setups on larger sites, see our WordPress performance monitoring guide.
Monitoring tools worth using:
- Google PageSpeed Insights: spot checks of Core Web Vitals for a single URL.
- Google Search Console: field data from real Chrome users, grouped by URL pattern.
- GTmetrix: a detailed waterfall of every request on the page.
- Query Monitor: database and hook debugging on a staging or development copy.
Third-party scripts deserve a separate check here. On sites serving UK visitors, PECR requires consent before non-essential cookies are set, so tracking scripts should load only after the visitor accepts them. Done properly, that also keeps them out of the first page load for everyone who has not yet clicked the banner.
Metrics to watch:
| Metric | Target | What it tells you |
|---|---|---|
| LCP (Largest Contentful Paint) | 2.5 s or less | Perceived loading speed |
| INP (Interaction to Next Paint) | 200 ms or less | Responsiveness to input |
| CLS (Cumulative Layout Shift) | 0.1 or less | Visual stability |
| TTFB (Time to First Byte) | under 200 ms | Server and cache performance |
The LCP, INP and CLS thresholds are Google’s published “good” values, measured at the 75th percentile of page loads (web.dev: Web Vitals). INP replaced FID as a Core Web Vital in March 2024, so any report still showing FID is out of date. The TTFB figure is this article’s own working threshold for when to look at the server, not a Google Core Web Vital.
Checklist for a slow WordPress site
If your site is slow, don’t guess, benchmark. Use Query Monitor to find expensive database calls and PageSpeed Insights to identify render-blocking scripts.
- Check your query count: Are you doing 100+ queries for a single page load?
- Audit your hero section: Is the main image served as AVIF or WebP rather than PNG/JPEG?
- Test your TTFB: If it’s over 200ms, then consider your hosting or database optimization.
- Review third-party scripts: How many external scripts load, and how long do they block the main thread?
The cause is often that “one simple plugin” nobody remembered to deactivate.
Learn more about professional WordPress development and a WordPress security audit at WPPoland.
For the delivery side of this topic we keep the work under Core Web Vitals audit.







