How to reduce server response time (TTFB)
A slow first response holds back every speed number. How to find where your server loses time, and the fixes in order: cache, database, outside calls, capacity.
In this guide
- What the number means
- Step 1: measure the server's share
- Fix 1: stop building the page every time
- Fix 2: make the work cheaper
- Fix 3: stop waiting on other services
- Fix 4: give the server enough room
- Fix 5: shorten the distance and skip the extras
- Where to look first on each platform
- Check that the fix worked
- Mistakes to avoid
- Check your own page
- Sources
What the number means
In Lighthouse 13.5, server response time is one of three checks in the insight called Document request latency. The check fails when the server takes more than 600 milliseconds to start responding to the request for your page. The other two checks in that insight are about redirects and compression.
The source code explains where 600 comes from. The throttling that Lighthouse uses means it cannot see a server response faster than about 570 milliseconds, so the failure line sits at 600 to avoid false alarms. The comment in the code says the real aim is 100 milliseconds. When the check fails, the saving that Lighthouse reports is the time above 100 milliseconds, which is why the estimate can look large.
This number is only the server's share. The full time to first byte also includes redirects, the address lookup and the connection. Lighthouse sets its limit at 600 milliseconds, below the 800 that counts as good for real visitors, for that reason. A page can pass here and still have a slow first byte for visitors far away. For them, see what a CDN does.
Step 1: measure the server's share
Test the page you care about, several times, and write the numbers down. This command prints the server's wait for five requests in a row:
for i in 1 2 3 4 5; do
curl -s -o /dev/null -w '%{time_pretransfer} %{time_starttransfer}\n' https://example.com/ \
| awk '{ printf "server wait: %d ms\n", ($2 - $1) * 1000 }'
doneIt works because curl's time_starttransfer includes everything up to the moment the transfer was about to begin (time_pretransfer), plus the time the server needed to calculate the result. The difference is the server's wait.
Now read the pattern in the numbers.
| What you see | What it usually means |
|---|---|
| The first request is slow, the next ones are fast | A cache fills on the first visit. If lifetimes are short or the cache is cleared often, many visitors still get the slow one |
| Every request is slow, by about the same amount | The page is built from scratch each time, or the server is slow |
| The numbers jump around | A busy or shared server, background jobs, or too few workers |
| Slow only when logged in, or in the cart | These pages skip the page cache by design |
| Slow only on certain pages | Work that only those pages do: a heavy query, a plugin, an outside call |
| A delay of nearly the same size every time, such as 5 seconds | A timeout: the page is waiting for a service that does not answer |
To see which part of the build is slow, have your code report it with the Server-Timing header. The time to first byte guide shows how. A performance monitoring service for applications can do the same job without code changes.
Fix 1: stop building the page every time
A dynamic site such as WordPress runs code and asks the database questions to build every page. A page cache saves the finished HTML and sends it back for the next request. It is the largest gain for most sites, and the first thing to check.
On WordPress, a page cache works like this. If the setting WP_CACHE is on and a file called advanced-cache.php exists in the content folder, WordPress loads that file very early in start-up, before most of its own code runs. That is how a caching plugin can return a saved page without waking up the rest of WordPress. Many hosts do the same job at the server level. Ask your host what it provides before you add a plugin, because two page caches can fight each other.
Decide what must not be cached:
- Pages for logged-in users, carts, checkouts and account pages. WordPress sends no-cache headers for logged-in users, 404 pages and password-protected posts, so a good cache respects them.
- Any page whose content depends on a cookie.
For pages that change often, even a short cache helps. web.dev notes that on a busy site only the first visitor in each cache period waits for the full trip to the server, and that caching a dynamic page for a few seconds can cut the load noticeably with little effect on freshness. A site that is mostly static can go further and build its pages ahead of time, so there is nothing to compute at request time.
See caching explained for the other layers, and HTTP caching headers to set the lifetimes.
Fix 2: make the work cheaper
When a page cannot be cached, make building it cheaper.
Use a current PHP version, with OPcache on. For PHP sites, OPcache stores the compiled form of each script in shared memory, so PHP does not load and parse the scripts again on every request. It has been bundled with PHP since version 5.5. Ask your host that it is enabled and that the PHP version is still supported.
Add a persistent object cache. WordPress has an object cache, but by default it lives only for the length of one request, so nothing is remembered for the next. A persistent one, such as Redis or Memcached, keeps database results and settings in memory between requests. It is installed through a file named object-cache.php in the content folder. WordPress's Site Health screen tests for this. It recommends a persistent object cache when the site is a multisite network, has more than 500 autoloaded options or more than 100,000 bytes of them, or has 1,000 or more comments, options, posts, terms or users. Drupal's advice from Lighthouse is the same in spirit: PHP opcode caching, and memory caching with Memcached or Redis to cut database query times.
Trim the autoloaded options. WordPress reads every autoloaded option on every page load. Plugins that were removed can leave theirs behind. Site Health reports a problem when they add up to 800,000 bytes or more. Look under Tools, then Site Health, then Status. Removing options safely is covered in the WordPress database cleanup guide in this series.
Cut what runs on every request. Each plugin and theme feature adds code and queries to every page. Lighthouse's advice for WordPress is to look for a more optimized theme, to choose an optimization plugin carefully and to upgrade the server. Test on a copy of the site: turn a plugin off, measure again, and keep it off if nothing breaks.
Fix the slow queries. If one page does something expensive in the database, such as an unindexed search or a large report, no cache layer will hide it for long. Use a query profiler to list the slowest queries on a slow page. Lighthouse's advice is to optimize how the server queries the database, or to move to a faster database system.
Fix 3: stop waiting on other services
A page that calls another service while it is being built is as slow as that service. Typical cases are a social feed, a price or stock lookup, a license check, an advertising call and a shipping calculator.
WordPress gives each outside request 5 seconds by default. If a plugin calls a service that is down, every page that needs it can wait 5 seconds before it gives up. That matches the pattern in the table, a delay of nearly the same size every time.
The cure is to take the call off the path of the page:
- Remove it if nobody needs it.
- Keep the result for a few minutes or hours, in the object cache or in a saved value, and refresh it in the background.
- Load it in the browser after the page, so the page itself does not wait for it.
- Shorten the timeout where the plugin or code lets you.
Fix 4: give the server enough room
If the numbers jump around, the server may be short of memory, processor time or workers.
A PHP server runs a fixed number of workers, and each worker handles one request at a time. In PHP-FPM that limit is the setting pm.max_children. Its comment in the PHP source says it sets the limit on the number of simultaneous requests that will be served, and that the sample default of 5 suits a server without much resources. When every worker is busy, the next request waits in line, and that wait counts as server response time. Slow requests make it worse, because each one holds a worker for longer.
So the order of work is: first make requests shorter with the fixes above, then add workers if the memory allows, then move to a bigger plan. Lighthouse lists more memory or processor time as one of the ways to cut server response time. The hosting guide explains what to compare when you do. Background work such as backups, imports and scheduled tasks should not run in the busiest hours.
Fix 5: shorten the distance and skip the extras
- Put the page in front of a CDN that is allowed to cache it. See what a CDN does.
- Remove redirects between the link and the final address. The same insight fails the page when it was redirected.
- Stream the HTML so the browser gets the top of the page while the rest is still being built, and consider
103 Early Hintsfor sites with a long build. Both are in the time to first byte guide. - Turn on text compression for the HTML. The third check in the insight is about it.
Where to look first on each platform
| Platform | First places to look |
|---|---|
| WordPress | Page cache, persistent object cache, plugins and theme, autoloaded options, PHP version and OPcache, hosting plan |
| Drupal | The theme and modules, PHP opcode caching, a memory cache such as Memcached or Redis |
| Magento | A Varnish integration, which is Lighthouse's own suggestion |
| Server-rendered React | Streaming the rendered HTML instead of waiting for the whole page |
| Static sites | Usually fast already; add a CDN for distance |
Check that the fix worked
- Run the same measurement again, at least five times, and compare the median.
- Test both paths: a request that is answered from the cache, and one that is not, such as the first request after you clear it. The slow path is what a new visitor and a search engine crawler may meet.
- Make an edit and check that it appears. A cache that serves old pages is worse than a slow server.
- Look at the real-visitor first-byte time in a month's time. Field data covers a rolling 28 days, so it moves slowly. See lab data and field data.
Mistakes to avoid
- Measuring once, or only while logged in.
- Adding several speed plugins that each add code to every request. Choose one that fits and remove the rest.
- Raising the memory limit when the problem is one slow query.
- Treating 600 milliseconds as the goal. It is the line for a failing mark, and the target in the code is 100.
- Caching pages that carry private data.
Check your own page
Run the free audit and look at Document request latency. If the server response line fails, run the measurement above from a terminal and match the pattern to the table. If it passes, the first byte is not your main problem, and the audit's other items will give a bigger gain.
Sources
- Reduce server response times on Chrome for Developers: the 600 millisecond mark, why it is below the TTFB mark, and the stack-specific advice.
- Optimize Time to First Byte on web.dev: hosting, caching, redirects, streaming and Early Hints.
- Content delivery networks (CDNs) on web.dev: caching dynamic content for a few seconds.
- Site Health tests in WordPress core: the object cache and autoloaded options checks and their thresholds.
- Object cache in the WordPress developer reference.
Start here
Free website audit
Live Google data for any public page: mobile and desktop.
Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it
Rather have this done for you? Speed optimization · Book a call