Skip to content
ThemesIonic — home
Troubleshooting

Reduce Initial Server Response Time (TTFB) in WordPress

A high TTFB is either a cache that is not being hit or a PHP request that does too much work. Measure which one you have before changing anything, because the fixes do not overlap.

Updated 6 min read intermediate

Some links on this page are affiliate links: if you buy through one, we may earn a commission at no extra cost to you. It does not change what we recommend; see the editorial policy.

Time to first byte is two problems wearing one number: a page cache that is not being hit, or an uncached request that does too much work. The fixes for each are completely different, so the first job is to find out which one you have.

TTFB is everything that happens between the browser sending a request and the first byte of HTML arriving: DNS, connection and TLS setup, the trip to the server, and the time the server spends building the response. When GTmetrix says "waiting for server response" or Lighthouse says "Reduce initial server response time" (called "Document request latency" in current PageSpeed Insights), it is the last part that is usually at fault.

Measure it properly

A browser test mixes in your own network. curl isolates the server:

curl -s -o /dev/null -w "connect: %{time_connect}s  tls: %{time_appconnect}s  ttfb: %{time_starttransfer}s  total: %{time_total}s\n" https://example.com/

Run it three times, logged out, from a terminal with no cookies. Then look at the headers:

curl -s -D - -o /dev/null https://example.com/ | grep -iE "cache|age|x-"

Look for x-cache: HIT, cf-cache-status: HIT, an age header, or a host-specific equivalent — reading them is covered in how to check HTTP headers.

What you see Likely cause
Under 200 ms, cache HIT Healthy; the problem is elsewhere
Fast second request, slow first one Cache works but expires or gets purged too often
Slow every time, no cache header No page cache at all
Slow every time, cache MISS or BYPASS Cache exists but a cookie, query string or rule skips it
600 ms–2 s uncached Heavy plugins, large autoload, old PHP
Over 2 s uncached, varying wildly CPU throttling, external HTTP calls, cron on page load

TTFB minus time_appconnect is roughly the time the server spent building the page; time_appconnect itself is network and TLS. If that alone is 300 ms, the server is far from you, and a CDN that caches HTML at the edge helps more than any server tuning — Cloudflare and WordPress covers that trade-off.

If there is no page cache

A cached page is served without running PHP, and its TTFB drops from hundreds of milliseconds to tens. Most managed hosts already do this at the server level; check before adding anything, because a plugin cache on top of a server cache means double purging and stale pages. The plugin options are compared in choosing a cache plugin.

On hosting without server-level caching, a plugin is the fix. WP Rocket enables page caching and cache preloading on activation, which matters here: preloading means the first visitor after a purge does not pay the uncached cost.

Be clear about what this buys. A page cache fixes TTFB for anonymous visitors on cacheable pages. Logged-in users, the cart, checkout, search results and anything personalised still run PHP on every request, so the uncached path below still matters.

If the cache is skipped

A cache that exists but reports MISS or BYPASS on every request is usually being bypassed by one of these; how exclusion rules are written is in cache exclusions:

  • a cookie set for every visitor by a consent, analytics or A/B testing script;
  • query strings from ad campaigns (?utm_source=..., ?fbclid=...) not being ignored;
  • an exclusion rule broader than intended, or a theme that starts a PHP session on every page.

Test with a plain request and then with the cookie or query string added. When only one is slow, the fix is a cache rule, not more hardware.

Making the uncached request faster

This is the TTFB of every logged-in page and every cache miss.

PHP version. PHP 8.x is substantially faster than 7.x, and each 8.x release adds smaller gains. Upgrade on staging first; what breaks after a PHP update lists the usual casualties.

Autoloaded options. WordPress loads every autoloaded option on every request. Plugins that were removed often leave theirs behind:

SELECT option_name, LENGTH(option_value) AS bytes
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto')
ORDER BY bytes DESC
LIMIT 20;

If the total is above about 800 KB (the point where Site Health warns), it is worth trimming — sum the column instead of listing it to see the total, and database optimization covers which rows are safe to change. Back up first, and set leftovers to not autoload rather than deleting rows you do not recognise.

Slow plugins. Query Monitor shows the total time, database queries and HTTP calls per request, grouped by the plugin that caused them. One plugin running hundreds of queries on every page is common and is visible within a minute.

External HTTP calls. A plugin that calls a remote licence server, an API or a font service during page generation makes your TTFB depend on someone else's server. Query Monitor's HTTP API panel lists them. Anything synchronous on the front end is a candidate for removal or caching.

WP-Cron. By default, scheduled tasks are triggered by visitor requests, The job runs in a separate background request, so the visitor rarely waits for it directly, but a heavy backup or feed import competes with real visitors for the same PHP workers and CPU. Move it to a real system cron — disabling WP-Cron shows how.

Object cache. Redis or Memcached keeps WordPress's object cache — options, posts, metadata, transients — in memory between requests, so repeated lookups skip the database. On WooCommerce and membership sites, where much of the traffic cannot be page-cached, this is often the largest single gain.

Hosting limits. On shared hosting, CPU is rationed. TTFB that is fine at night and two seconds at noon, with no change to the site, is throttling — no plugin fixes that. How the server stack fits together explains where the time goes, and comparing WordPress hosting covers when moving is the answer.

Common mistakes

  • Measuring while logged in. Most caches skip logged-in users, so you measure the uncached path and conclude the cache does nothing.
  • Testing once after a purge. The first request rebuilds the cache; the second one is the real number.
  • Adding a second cache plugin on a host that already caches, then chasing stale pages.
  • Minifying CSS to fix TTFB. Front-end optimisation happens after the first byte; it cannot change this number.
  • Blaming WordPress for distance. A 300 ms connection time is geography, and a CDN that caches the HTML at the edge is the fix.

Verify

Run the curl command three times on the homepage and on one interior page, logged out. The cached result should be consistent and well under 200 ms, with a HIT header. Then test a page that cannot be cached, such as the cart or a search result, and compare it against the numbers you recorded before you started. Field data in Search Console takes about four weeks to reflect the change.

Frequently asked

Under about 200 ms for a cached page and under 600 ms for an uncached one is healthy. Lighthouse has long flagged anything over 600 ms as "Reduce initial server response time", and that warning almost always means the test hit the uncached path.
A page cache plugin reduces it for cached pages, because PHP no longer runs. No plugin makes the uncached path faster on its own; that depends on PHP version, database size, the plugins you run and the server's CPU.
Tools test from different locations, and the first request after a purge is uncached. A tool far from your server adds network latency, and one that sends cookies or query strings may bypass the cache entirely.

Related guides