WordPress TTFB: The Number Everyone Quotes And Almost Nobody Measures Properly.
We measured TTFB on three real sites and bypassed their caches. One got 9x slower. Here is what the number is made of and which part you can fix.
Short answer: wordpress ttfb is not a measure of your server. It is DNS, plus a TCP connection, plus a TLS handshake, plus however long the server took to think. Only the last part is yours. And if you measured it as an ordinary visitor, you may have measured a cache rather than the site at all.
I do this measurement often at POSIMYTH, usually when somebody sends me a number and asks whether it is bad.
The honest answer is almost always: I cannot tell yet.
What the number is actually made of
Here are three real sites measured from the same machine, minutes apart, with the time split into its parts.

segment is server think time, and it is the only segment your hosting and your WordPress install control. Yes, the slowest one is ours.*
Look at woocommerce.com. Total 74ms, of which the server spent 19ms.
The other 55ms was getting a connection open. No amount of caching plugin will change that part, because it is the speed of light and some certificate maths.
Now look at ours. 851ms total, 665ms of it server think time.
That is a genuinely different problem from wordpress.org’s 770ms, where the server accounted for 256ms and the connection setup for the rest. Two similar totals. Two unrelated causes.
This is why a bare TTFB comparison between two sites is close to meaningless. A site in Frankfurt measured from Mumbai will lose to a worse-built site next door.
The mistake that makes the number a lie
Here is the part I care most about, because it is the one that produces confidently wrong client reports.
When you load a site normally, you may be talking to a cache. So you are measuring the cache.
Append a query string nobody has ever requested and the cache has to go to the origin:

stop letting it answer from cache. That 77ms was real for visitors and completely wrong as a description of the server.*
Nine times.
Both numbers are true. They answer different questions. The cached number tells you what a visitor experiences. The bypassed number tells you what happens when the cache misses, which is exactly what happens to the first visitor after every deploy, every purge, and every uncached page like a cart or a checkout.
If you only ever measure the first one, you will be surprised by the second one on a Friday.
How to measure WordPress TTFB properly, in one command
curl -s -o /dev/null \
-w 'dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer}\n' \
https://example.com/
Read it as differences, not as four separate numbers:
- DNS is
time_namelookup - Connect is
time_connectminus DNS - TLS is
time_appconnectminustime_connect - Server think time is
time_starttransferminustime_appconnect
That last subtraction is the whole trick, and it is the number worth writing down.
Then run it again with ?cachebust=$RANDOM on the end and write down both.
Run it three times, take the fastest, and note where you measured from. A single sample from a café wifi is not a measurement.
Read the headers before you believe anything
Before diagnosing, find out whether a cache was involved at all.

Cloudflare answers DYNAMIC, meaning it is not being cached at the edge at all. I did not go looking for that. It turned up while measuring for this post, which is rather the point of measuring.*
That gap between “what the site asks for” and “what the cache did” is where most performance confusion lives.

cache-control is written by your ownsite and only states an intention. Every other header here was written by something that actually made a decision.*
So what do you do with a slow number
Work in this order, because it goes cheapest to most disruptive.
Confirm which number is slow. Cached and bypassed both slow is an origin problem. Only bypassed slow means your cache is working and your origin is weak, which is survivable but fragile.
Check the server think time specifically. If time_starttransfer minus time_appconnect is under about 200ms, your PHP is not the problem and you should stop optimising it.
If connection setup dominates, the fix is geography, not code. A CDN in front, or hosting nearer the audience.
Only then look at WordPress. Slow plugins, an unindexed query, a bloated autoload table, a theme calling an external API on every page load. A plugin that stopped being maintained is a common culprit here, because nobody has profiled it in years.
Most of the WordPress performance advice on the internet starts at step four. On a lot of the sites I look at, the answer was in step three, and somebody spent a weekend on step four.
The part that does not scale
None of this is hard. It is two commands and a subtraction.
The problem is that it is two commands per site, and the answer changes every time hosting changes, a CDN rule is edited, or a plugin starts calling something slow on every request.
You will do it thoroughly during onboarding, and then not again until a client complains. That is the same shape as the unbilled half hour that every other check falls into.
Protuno’s Performance agent, Dash, carries a server response time check that measures origin rather than edge, on a schedule, across a portfolio. Being straight with you as always: Dash is built and named but not live yet. The free audit does run a server response check today, from the domain alone, alongside the caching headers described above.
Until the rest ships, the curl line is genuinely enough. Put it in whatever runbook you keep, next to the check for scheduled jobs that quietly stopped.
And measure your own site first. I did, and it is the slowest one in the chart.
Comments