Why your WordPress site is slow (it’s probably not the hosting)
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. In my experience, throwing money at hosting before optimizing your application layer is a temporary fix for a permanent problem.
The myth of the “magic hosting” fix
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.
Practical performance fixes
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.
Keep monitoring after the fix
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.
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.
Summary: benchmarking over guesswork
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?
What’s the #1 thing that slowed down your site? Often it’s that “one simple plugin” you forgot to deactivate.
Learn more about professional WordPress development and a WordPress security audit at WPPoland.







