Time to first byte is how long the browser waits before the first byte of HTML arrives. Everything else about page speed (images, scripts, fonts) can only start after that byte. If your TTFB is 1.8 seconds, no image optimisation plugin in the world gets you a fast site, because the visitor stared at a blank tab for 1.8 seconds before the page even began. It’s also the number most speed plugins can’t touch, which is why so many “optimised” WordPress sites are still slow. Here’s how I measure it and what moves it.
Step 01 Measure it properly
Don’t trust one test. TTFB varies by page, by cache state, and by whether you’re logged in. Run these:
- Chrome DevTools → Network, reload the page, click the first document request, open the Timing tab. “Waiting for server response” is your TTFB. Do it logged out, in a private window, twice (the second run shows you the cached number).
- PageSpeed Insights reports TTFB under the field data if your site has enough traffic. Field data is what real visitors experienced; treat it as the truth.
curlfrom a terminal gives a clean number without browser noise:
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\n" https://example.com/
Test the homepage, a product or post page, and a page nobody visits (it won’t be cached, so it shows your uncached, real server time). Write the three numbers down.
What’s good Under 200 ms is excellent, under 500 ms is fine, over 800 ms is a problem Google’s Core Web Vitals will flag. If your uncached page is over 1.5 s, your hosting or your database is the story, and steps 02 and 04 are where the time is.
Step 02 Page caching, done right
A full-page cache means WordPress doesn’t run at all for most visitors; the server hands back a saved HTML file. This alone turns a 900 ms TTFB into 80 ms for cached pages. Most people have a caching plugin. Fewer have it working:
- Test a page logged out. If the response headers show
x-cache: HIT(or your host’s equivalent), it’s working. If every request is a MISS, the cache is being bypassed, usually by a cookie a plugin sets on every visitor, or by a query string. - Check the uncached case too. Caches expire; the first visitor after every expiry gets the slow page. That’s why step 04 still matters.
- On WooCommerce, cart, checkout, and account pages must never be cached; a correct setup excludes them and still caches products and categories.
If you’re on managed hosting, the host’s server-level cache beats any plugin. Use theirs, and remove the plugin that fights it.
Step 03 The host
The uncomfortable one. Shared hosting at $4 a month puts your site on a server with hundreds of others, and your TTFB is whatever is left over. If the uncached number from step 01 is over a second and your database is small, moving hosts is the single biggest lever, and no plugin substitutes for it. I say this as someone who does not sell hosting.
What to look for: PHP 8.2 or newer, a server-level page cache, an object cache (Redis or Memcached) included, and data centres near your visitors. Prices for good managed WordPress hosting start around $25 a month.
Hosting and page cache are doing their job. Your remaining slowness is on the page (images, scripts), not the server. The rest of this post is optional.
Something runs on every request. Step 04.
Step 04 The database and autoloaded options
Every WordPress request loads the wp_options rows marked autoload = yes into memory. On a healthy site that’s under 1 MB. On a site that’s had ten years of plugins installed and removed, I’ve seen 20 MB, loaded on every single page view. Check it:
SELECT SUM(LENGTH(option_value)) / 1024 / 1024 AS autoload_mb
FROM wp_options WHERE autoload = 'yes';
Over 2 MB and you’ve found time. The usual culprits are expired transients (_transient_% rows that never got cleaned), and options left behind by plugins that no longer exist. Delete expired transients, and set autoload = 'no' on the big rows from plugins you no longer use.
While you’re there: wp_postmeta orphans and revision bloat don’t slow TTFB much, but a database with 40 million rows of Action Scheduler logs does. wp_actionscheduler_logs and wp_actionscheduler_actions are worth a look on any WooCommerce site.
Step 05 Plugins that run on every request
Turn on the Query Monitor plugin (temporarily; it’s a diagnostic, not a resident). Load an uncached page and look at the slowest queries and the slowest plugins. What you’ll usually find:
- A plugin calling an external API on page load (licence checks, weather, currency rates, social counts). Every visitor waits for a third-party server.
- A security plugin logging every request to the database.
- A “related posts” plugin running a full-text query on every page.
- Heartbeat and cron firing on the front end.
Fix each one you find (settings, replacement, or removal), re-measure, and stop when the uncached number is under 600 ms. There is always something else to optimise; there isn’t always a reason to.
Step 06 Object cache and the CDN
Two finishers. An object cache (Redis) stores query results between requests, so the database work in step 04 happens once instead of every time; most managed hosts have a switch for it. A CDN doesn’t change TTFB for HTML unless it caches the HTML too (Cloudflare’s cache rules can), but it does move your images and scripts to a server near the visitor, which is the other half of feeling fast.
Then measure again, exactly as in step 01, and keep the numbers. A speed project without a before and an after is an opinion. When I do this for clients the receipt on the invoice is the two numbers, which is how a store went from 840 ms to 110 ms and everyone could see it.