Most WordPress speed guides are an unordered list of twenty things, which is why people minify their CSS and wonder why the site is still slow. The work has a priority order, and it is roughly: fix time to first byte, add page caching, fix images, then everything else. The last group is where most people start.
Speed work has a brutal pareto distribution. Two or three changes account for most of the improvement on a typical WordPress site, and the remaining fifteen items on the checklist share what is left. Here they are in the order that pays.
Table of contents
- Measure first, and measure the right thing
- 1. Hosting and PHP version — the biggest single lever
- 2. Page caching — the second biggest lever
- 3. Images — usually the largest bytes on the page
- 4. Plugins and the database
- 5. The frontend work, which comes last for a reason
- How this fits the rest of the stack
- FAQ
Measure first, and measure the right thing
Before changing anything, get a baseline you can compare against. Otherwise you cannot tell which change helped, and you will keep all twenty of them forever.
- TTFB (Time to First Byte) — how long the server takes to start responding. This is the server-side number and the one WordPress optimisation actually targets.
- LCP (Largest Contentful Paint) — when the main content appears. Usually an image or heading. Should be under 2.5 seconds.
- INP (Interaction to Next Paint) — responsiveness to input. Replaced FID in 2024. Should be under 200ms.
- CLS (Cumulative Layout Shift) — how much the page jumps as it loads. Should be under 0.1.
Distinguish lab data from field data. A synthetic test from a datacentre near your server is optimistic. Real user data from the Chrome UX Report reflects actual visitors on actual devices, and it is what search ranking uses.
# TTFB in isolation, no browser involved
curl -o /dev/null -s -w 'dns:%{time_namelookup} connect:%{time_connect} ttfb:%{time_starttransfer} total:%{time_total}\n' https://example.com/
# Cached versus uncached -- the gap tells you what caching is worth
curl -s -I https://example.com/ | grep -iE 'x-cache|age|cf-cache-status'
If TTFB is above about 600ms, stop reading the frontend advice. No amount of image compression fixes a server that takes a second to think.
1. Hosting and PHP version — the biggest single lever
This is unglamorous and it is usually the answer. A WordPress site on oversubscribed shared hosting has a TTFB floor you cannot optimise past, because you are waiting for CPU that other tenants are using.
Two changes here move the number more than anything else on this page:
- Run a current PHP version. PHP 8.x is substantially faster than 7.x, which was itself roughly twice the speed of 5.6. Sites still running 7.4 are leaving a large, free improvement on the table — and running unpatched.
- Give the site dedicated resources. Predictable CPU and memory, rather than a share of a busy machine.
Enable OPcache if it is not already on. It caches compiled PHP bytecode so the interpreter is not reparsing every file on every request, and the effect is large:
; php.ini
opcache.enable=1
opcache.memory_consumption=192
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1 ; 0 in production, with a deploy-time flush
opcache.revalidate_freq=2
On RunxBuild, managed WordPress runs on its own plan with dedicated vCPU and RAM — from $3/month for Starter up to WpPro at 2 vCPU and 4GB — so the resource question is a plan choice rather than a support ticket. Autoscaling handles the traffic spikes that otherwise take a shared host down.
2. Page caching — the second biggest lever
Every uncached WordPress request runs PHP, queries MySQL, and assembles HTML that is usually identical to the last one. Page caching stores the finished HTML and serves it directly.
The improvement is not incremental. A page that takes 800ms to generate takes about 20ms to serve from cache.
- Full-page cache at the server or plugin level. This is the one that matters.
- Object cache with Redis or Memcached, which caches database query results for the requests that cannot be fully cached.
- CDN edge caching so the HTML is served from a location near the visitor.
Get the exclusions right or you will ship broken pages:
- Never cache for logged-in users, or admins see stale content and each other’s sessions.
- Exclude cart, checkout, and account pages entirely.
- Exclude anything personalised — a cached page showing another user’s name is a data leak, not a bug.
- Purge on publish, on update, and on comment approval.
Object caching is the one people skip, and it is the one that helps a busy site. WordPress caches objects in memory per request by default, so every request re-runs the same queries. A persistent object cache carries them across requests. Note that RunxBuild does not offer managed Redis — if you need one, it runs elsewhere, and for most sites full-page caching delivers the bulk of the benefit anyway.
3. Images — usually the largest bytes on the page
Images are typically the majority of page weight and the LCP element. Three changes cover almost all of it.
- Serve modern formats. WebP is roughly 25-35% smaller than JPEG at equivalent quality; AVIF is smaller still. Browser support is universal enough that this is now a default.
- Serve the right size. A 4000px photo scaled to 800px in CSS downloads all four thousand pixels. WordPress generates size variants — make sure your theme uses
srcsetrather than hardcoding the full size. - Lazy-load below the fold, and only below the fold. WordPress adds
loading="lazy"automatically. Lazy-loading your LCP image actively delays it, so exclude the hero.
<!-- Above the fold: eager, high priority, explicit dimensions -->
<img src="/hero.webp" width="1200" height="630" alt="..."
loading="eager" fetchpriority="high" />
<!-- Below the fold: lazy -->
<img src="/photo.webp" width="800" height="600" alt="..."
loading="lazy" decoding="async" />
Always set width and height. Without them the browser cannot reserve space, the page reflows when each image loads, and your CLS score suffers for no reason. This one attribute pair fixes most layout-shift problems on content sites.
4. Plugins and the database
Plugin count is a poor proxy for speed — one badly-written plugin outweighs twenty efficient ones. What matters is what they do on each request.
Query Monitor shows you exactly that: queries per page, slowest queries, HTTP calls made during the request, and which plugin is responsible.
- Plugins making external HTTP calls during page load are the worst offender. Every page render waits on someone else’s API.
- Plugins loading assets on every page — a contact form’s CSS on all 400 pages.
- Autoloaded options. Every request loads every option flagged
autoload=yes. Plugins that store large blobs there tax every single request forever, including after uninstall.
-- How much is loaded on literally every request?
SELECT ROUND(SUM(LENGTH(option_value))/1024) AS autoload_kb
FROM wp_options WHERE autoload = 'yes';
-- The worst offenders
SELECT option_name, ROUND(LENGTH(option_value)/1024) AS kb
FROM wp_options WHERE autoload = 'yes'
ORDER BY LENGTH(option_value) DESC LIMIT 20;
Anything above about 800KB autoloaded is worth investigating. Orphaned options from removed plugins are common and safe to clean once identified.
Also clear the accumulated junk: post revisions, expired transients, spam comments, and orphaned metadata. wp_options and wp_postmeta grow indefinitely and nothing prunes them by default.
5. The frontend work, which comes last for a reason
Minification, concatenation, critical CSS, font optimisation, and script deferral. All worthwhile, all much smaller than the four above.
- Defer non-critical JavaScript. Render-blocking scripts in
<head>delay first paint. - Preload the LCP image and primary font so the browser starts fetching them immediately rather than after parsing CSS.
- Self-host fonts with
font-display: swap. Third-party font hosting adds a DNS lookup, a connection, and a privacy consideration. - Audit third-party scripts ruthlessly. Analytics, chat widgets, and ad tags are frequently the single largest cost on the page and nobody owns them.
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
<link rel="preload" as="font" type="font/woff2"
href="/fonts/inter.woff2" crossorigin>
<link rel="preconnect" href="https://analytics.example.com">
On third-party scripts specifically: measure before defending them. Disable JavaScript, reload, and compare. A chat widget that costs 400ms of INP is a business decision, not a technical one, and the business cannot make it without the number.
Minification is genuinely the last item. Compressing 40KB of CSS to 32KB is real and it is not why your site is slow.
How this fits the rest of the stack
Fix TTFB, add page caching, fix images, then audit plugins. Those four cover the large majority of the available improvement, and the popular checklist items sit underneath them. Measure before and after each change so you keep the ones that worked and drop the ones that were cargo cult. If you are comparing WordPress plans and want the site and its database as separate line items, the RunxBuild hosting calculator shows them apart.
Useful related references:
- Dockerfile COPY: The Instruction Order That Decides Your Build Speed
- Cloud Cost Optimization: The Ten Things That Actually Move the Bill
- Cloud Cost Optimization Tools That Actually Move the Number Down
- Services on RunxBuild
FAQ
What is the single biggest WordPress speed improvement?
Usually hosting and PHP version, measured as time to first byte. If TTFB is above roughly 600ms, no amount of frontend optimisation will make the site feel fast — you are waiting on the server before anything else can happen.
Do I need both page caching and object caching?
Page caching gives the larger win and should come first. Object caching helps the requests that cannot be fully cached, such as logged-in sessions and dynamic pages, and matters most on busy or e-commerce sites.
Why is my Core Web Vitals score bad despite a fast server?
LCP and CLS are frontend measures. A fast server with an unoptimised hero image, no width and height attributes, or heavy third-party scripts will still score poorly. Fix the LCP image and set explicit dimensions first.
Does the number of plugins affect WordPress speed?
Less than people assume. One plugin making an external HTTP request on every page load costs more than twenty efficient ones. Use Query Monitor to see what each actually does per request rather than counting them.
Should I lazy-load all images?
No. Lazy-loading your largest above-the-fold image delays the LCP element and makes the score worse. Load the hero eagerly with fetchpriority=“high” and lazy-load only what starts below the fold.