How to reduce server response time (TTFB) in WordPress
Time to First Byte is the wait before a page's answer starts to arrive. Time it in phases, with and without the cache, to see whether the connection or WordPress is slow. Then fix that part, starting with a page cache, an object cache and the PHP version.
- By
- WP Ministry
- Published
- Tested on
- WordPress 7.1.3, PHP 8.3.35
In short
- Google's guide counts a TTFB of 0.8 seconds or less as good and more than 1.8 seconds as poor. It is not a Core Web Vital.
- TTFB adds up redirects, the DNS lookup, the connection and the server's own work. Time each part before changing anything.
- A cached page can hide a slow server. Time the same page with the cache and without it.
- A page cache answers visitors who are not logged in. A persistent object cache helps the pages that cannot be cached.
- Query Monitor shows which plugin, query or outside request a slow page spent its time on.
- When uncached pages stay slow with the plugins off, the server is the limit.
Time to First Byte (TTFB) is how long a browser waits between asking for a page and receiving the first byte of the answer. Nothing else on the page can begin until then, so a slow first byte makes every later figure slow.
The wait has two parts: reaching the server, and the server producing the page. On WordPress the second part is PHP running and the database being queried. Time each part, then fix the slow one. For the server's part the fixes, in order, are a page cache, a persistent object cache, a current PHP version, removing the slow plugin, a lighter database, a CDN and, last, a different host.
This page is about the server's part only. For images, scripts and everything after the first byte, see how to speed up WordPress.
What counts as a good TTFB
Google's guide to the metric adds up everything before the first byte: redirects, the DNS lookup, the connection and its TLS negotiation, and the request itself up to the moment the answer starts to arrive. It gives 0.8 seconds or less as good and more than 1.8 seconds as poor, and calls both a rough guide.
TTFB is not a Core Web Vital. The same guide says a site need not meet the good threshold so long as the metrics that matter do well. It counts because it comes before Largest Contentful Paint and adds to it. How to improve WordPress Core Web Vitals covers those figures.
WordPress has a figure of its own. Site Health asks the site for its own home page three times and takes the middle result. Under 600 milliseconds counts as good. That request starts on the server itself, so it leaves out the distance to a visitor. Do not compare it with the 0.8 seconds above.
Measure it
Use curl, a command-line tool that can report how long each phase of a request took. The commands below are written for a terminal on macOS or Linux. Put your own address in place of example.com.
Step 1: Time one request in phases
Every figure is in seconds, counted from the start of the request. To get one phase, subtract the line above it.
bashcurl -s -o /dev/null -w "dns %{time_namelookup}\nconnect %{time_connect}\ntls %{time_appconnect}\nfirst-byte %{time_starttransfer}\ntotal %{time_total}\nstatus %{http_code}\n" https://example.com/Step 2: Check the status
The last line should be
200. A301or302means the address redirects and you timed the redirect, because curl does not follow one unless told to. Use the final address, withhttpsand with or withoutwwwand the closing slash, as the site itself writes it. The redirect checker shows every hop.Step 3: Run it several times
One run is not a measurement. Run the command five times over a few minutes and set aside the odd one out. A run that finds no stored copy is slower, because that request has to build the page and fill the cache.
Step 4: See whether a cache answered
This prints the response headers. Look for the ones WordPress's own Site Health check reads as a cache hit:
cf-cache-status,x-cache,x-litespeed-cache,x-cache-statusorx-proxy-cachewith the valueHIT, or anageabove zero.Ageis the number of seconds the answer has spent in a cache. A plugin may mark the page instead: WP Super Cache adds a line with the time the page was cached at the end of the page's source.bashcurl -s -o /dev/null -D - https://example.com/Step 5: Time the same page without the cache
Google's guide warns that caching can hide a slow server, and suggests a URL parameter as a way past it. Change the number on each run. Then repeat the header check with a number you have not used yet, because a cache that treats the parameter as a new page keeps the one a run has just built. If it still reports a hit, the cache does not treat the parameter as a new page, and its documentation will say how to bypass it.
bashcurl -s -o /dev/null -w "first-byte %{time_starttransfer}\n" "https://example.com/?ttfbtest=1"
How to read the first command:
| Line | Counted from the start until | Subtract the line above to get |
|---|---|---|
dns | the name was looked up | (nothing above it) |
connect | the connection to the server was open | the time to connect |
tls | the secure handshake was done | the handshake |
first-byte | the first byte arrived | sending the request, the server's work and the trip back |
total | the whole page was received | the download of the HTML |
Write down three figures: tls, the cached first-byte and the uncached first-byte.
curl sends no cookies, so the server sees a visitor who is not logged in. That is the visitor a page cache is built for. WP Super Cache's listing, for one, says its static files go to users who are not logged in, have not left a comment and have not viewed a password-protected post. A browser where you are logged in may be showing you the uncached page, so test in a private window.
PageSpeed Insights shows TTFB for real visitors, when the page has enough of them. Google notes that its lab audit of server response time leaves out the DNS lookup and redirects, so it covers only part of TTFB.
Find which part is slow
The connection
If tls is a large share of first-byte, the time goes before WordPress starts: in the DNS lookup, or in the distance between the visitor and the server. Your own computer may be nearer to the server, or farther from it, than your visitors are. Run the command from where they are if you can.
A redirect comes on top, as a whole request made before this one. Google's guide calls redirects a common cause of a high TTFB, often from a link without https:// or with the wrong closing slash. Point your menus, ads and newsletters at the final address.
Fast from the cache, slow without it
This is what a working page cache looks like. Visitors who are not logged in get the fast copy. The slow build is still felt in the dashboard, by logged-in members, in a store's cart and checkout, and by the first visitor to a page whose copy has expired. If those matter to you, carry on to Query Monitor.
Slow with no cache hit
If the header check shows no hit and first-byte minus tls is large on ordinary pages, every visitor waits while WordPress builds the page. Start with the page cache below.
Which plugin, query or outside request
Query Monitor, from the WordPress.org plugin directory, adds a menu to the admin toolbar with an overview of the page you are on. By default only administrators see it. Its listing says its own effect on the time a page takes to build is negligible.
Open a slow page while logged in as an administrator and look at three things.
- Queries grouped by component. Query Monitor totals the database queries, and the time they took, for each plugin, for the theme and for WordPress itself. A plugin that accounts for most of the time is your lead.
- Slow and duplicate queries. It marks a query that took longer than its threshold and lists queries that ran more than once.
- HTTP API requests. These are calls WordPress made to other servers while it built the page. Each is shown with the component that made it and the time it took. By default WordPress waits up to 5 seconds for such a call, and the page waits with it.
A Server-Timing header
If the header check prints a server-timing line, the server is reporting its own account of the request. Each entry is a name with an optional duration, as in db;dur=53, app;dur=47.2. Your browser's developer tools show the same entries beside the request. A site sends this header only when something on the server is set up to.
The server itself
Copy the site to a staging site, switch every plugin off, and time an uncached page. If it is still slow, the plugins are not the cause. A dashboard that is slow on every screen points the same way. Ask the host whether the account is reaching its limits. WordPress's documentation names disk I/O and the number of processes among the limits of a shared plan.
Fix it, in order of effect
1. A page cache
A page cache keeps the finished HTML and hands it over without building the page again. For visitors who are not logged in, that takes the build out of the wait, which is why it comes first.
Check whether your host already runs one before adding a plugin. WordPress caching plugins compared shows how to check and how to choose, and how to speed up WordPress has the steps.
One thing to look for afterwards: Google's guide notes that tracking parameters added to links can make a CDN treat each address as a new page and skip its stored copy. If visitors from ads or newsletters get uncached pages, look for the setting that ignores those parameters.
How to see that it worked: the header check reports a hit, and the cached first-byte falls. On a live site, go to Tools, then Site Health. The Status tab reports "Page cache is detected and the server response time is good".
2. A persistent object cache
WordPress keeps what it reads from the database in an object cache, but by default only for the length of one request. A persistent one keeps it between requests. WordPress's documentation says the saved trips to the database lower TTFB, and gives the site's settings as the example: without it they are read from the database on every page view.
This is the fix for pages a page cache cannot hold. It needs a cache server such as Redis or Memcached, so it is your host's to offer. Site Health shows "You should use a persistent object cache" when the site holds enough data to gain from one, and "A persistent object cache is being used" once it is on. To go back, switch it off where you switched it on. For a store, see how to speed up WooCommerce.
3. A current PHP version
WordPress recommends PHP 8.3 or greater, and its documentation says newer versions of PHP are made with performance in mind. The WordPress and PHP end-of-life checker shows whether your version still gets security fixes.
Take a backup, bring plugins and the theme up to date, then change the version in the hosting panel. The steps, and how to go back, are under "Hosting and the PHP version" in how to speed up WordPress.
Ask the host one more thing: whether OPcache is on. It keeps the compiled form of PHP files in memory, so PHP does not load and parse them on every request.
4. Remove or replace the slow plugin
Act on what Query Monitor showed. In order of least disruption: switch off the one feature that makes the slow query or the outside call, replace the plugin with a lighter one, or remove it.
Do it on a staging copy first, and time the uncached page before and after. How to find a plugin conflict has the method for switching plugins off safely. To undo, activate the plugin again.
5. The database
Start with autoloaded options. These are plugin and theme settings that WordPress loads on every page. Its documentation says to keep them under 800 KB, and Site Health shows "Autoloaded options could affect performance" when they are over. With WP-CLI, this prints their size in bytes. The four values in it are the ones WordPress loads on every page. If your table prefix is not wp_, change the table name to match.
wp db query "SELECT SUM(LENGTH(option_value)) AS bytes FROM wp_options WHERE autoload IN ('yes','on','auto','auto-on');"A single slow query that Query Monitor traces to a plugin you need is a job for the plugin's author or a developer. Revisions and expired transients are covered under "The database" in how to speed up WordPress.
6. A CDN that caches pages or sits closer to visitors
A CDN in front of the site can shorten the connection, because the visitor connects to a server near them. That alone does not shorten the server's work. Cloudflare's documentation says it does not cache HTML by default, and cf-cache-status: DYNAMIC on a page means the request went on to your server.
To take the build out as well, the CDN has to cache the HTML. Some CDNs offer that, and it must leave out the dashboard and every personal page. How to set up a CDN for WordPress covers the setup and what goes wrong.
7. A different host
This is last because it is the most work, not because it matters least. Google's guide says hosting is the first thing to consider and that shared hosting is generally slower.
Move when the staging test above was slow with every plugin off, or when the host confirms the account is at its limits. Before you choose, ask the new host how much memory the site gets, which PHP versions it offers, whether a persistent object cache and a server page cache are included, and where the server is. The website migration checklist covers the move.
What not to rely on
- One test of a cached page. It shows how fast the cache is, not the server behind it.
- Caching personal pages to bring the number down. The cart, the checkout and account pages must be built for each visitor.
- A new host before you have measured. A slow plugin moves with the site.
When to get help
Rewriting a slow query, changing how a plugin calls another service, and tuning the server are a developer's work. Our speed optimization service is a one-time job: you name the pages, and we measure them before the work and again after it.
Common questions
Why is the dashboard slow when the site is fast?
Visitors get pages from the page cache. The dashboard does not, so every screen is built by PHP and the database. A persistent object cache, a current PHP version and fewer heavy plugins help it. If it stays slow, ask the host about the plan's limits.
Why do different tools show different TTFB figures?
They measure different things. A lab test loads the page once from its own location. Field data comes from real visitors and includes redirects. Google's lab audit of server response time leaves out the DNS lookup and redirects. Site Health asks from the server itself. Keep to one method when you compare before and after.
Why is the first load slow and the second fast?
The first request found no stored copy, so WordPress built the page and the cache kept it. The second was served from the copy. Some caching plugins, WP Super Cache among them, can preload pages so that fewer visitors meet the slow build. WordPress caching plugins compared says what each one does.

