When a website feels slow, the most common reaction is to compress images, install caching plugins, or increase hosting capacity. These steps can help, but they may not address the root cause. A website that is slow because the server is delayed in sending HTML requires different handling than a page that quickly receives HTML but is still busy loading JavaScript and images.
That’s why the first step is not to immediately change configurations, but to look for the bottleneck—the part that most hinders the process. In practice, we can start from two important indicators: Time to First Byte or TTFB, which is the time until the browser receives the first byte from the server, and Largest Contentful Paint or LCP, which is the time until the main content visible on the screen is fully displayed.
Google considers LCP one of the Core Web Vitals, aiming for a good experience at around 2.5 seconds or faster for the majority of visits. However, that number should be used as a diagnostic guideline, not the sole optimization goal. web.dev's guide on LCP also emphasizes the importance of looking at TTFB and the stages of resource loading to understand the source of the problem.
Start with a simple question: where is the slowness occurring?
Imagine a website like a restaurant. TTFB describes the time it takes for the waiter to bring the menu after we sit down. LCP is more like the time until the main dish is actually served. If the menu takes a long time to arrive, the problem may be in the kitchen, the order queue, or the cash register system. If the menu arrives quickly but the main dish takes a long time, attention needs to shift to the cooking and serving process.
On a website, the simple pattern is as follows:
- High TTFB: check the server, PHP, database, network, or page cache.
- Low TTFB but high LCP: there is usually a main image, font, CSS, JavaScript, or rendering process that is delayed.
- Both high: there may be a combined issue, for example, the page is slowed down by a database query and is still loading too many assets.
Use PageSpeed Insights, Lighthouse, or the Network tab in DevTools. Don’t just look at the final score. Check when the HTML document starts being received, which resources are candidates for LCP, and whether important resources are only found after JavaScript runs.
If TTFB is high, check the server before blaming images
High TTFB often arises because the server needs to perform too much work before sending a response. On PHP websites, the causes can include heavy plugins or modules, repeated authentication processes, connections to external services, or MySQL queries that read too many rows.
Check the logs and application execution times first. If using PHP-FPM, take advantage of settings like slowlog to find scripts that run unusually long. PHP-FPM indeed provides process management, logging, and slowlog to help identify slow PHP executions. Detailed settings are available in the PHP-FPM manual and configuration documentation.
Do not immediately increase the number of PHP-FPM workers. Too many workers can quickly exhaust RAM and trigger swapping or new queues. Instead, record memory usage, the number of concurrent requests, response times, and how many processes are waiting. Adjustments should follow the machine's capacity, not just copy numbers from tutorials made for different servers.
Databases are often a source of unseen queues
A seemingly simple page can run many queries behind the scenes. The problem becomes more pronounced on search pages, catalogs, dashboards, or websites with continuously growing data. Queries that were fast when the table contained thousands of rows can slow down significantly when the data volume increases dramatically.
Use EXPLAIN to see how MySQL plans to execute the query. This command can show which tables are read, which indexes are considered, which indexes are chosen, and the estimated number of rows that need to be checked. MySQL also provides EXPLAIN ANALYZE to compare the optimizer's estimates with actual execution times. The official reference is available in the MySQL Reference Manual.
EXPLAIN SELECT id, title
FROM posts
WHERE status = 'publish'
ORDER BY published_at DESC
LIMIT 20;From here, look for signs such as large table scans, expensive sorting, or filter columns that do not have the necessary indexes. However, indexes are not a cure-all. Too many indexes can also add overhead when writing data and consume storage space. Test changes to queries and index structures, then measure again.
Caching helps, but exclusion rules are more important
Page caching can reduce the workload on PHP and the database for the same page. On public websites like blogs or informational pages, this often has a significant impact. Nginx, for example, provides fastcgi_cache to store responses from PHP-FPM and reuse them on subsequent requests.
Problems arise when caching is applied without understanding the types of pages. Shopping carts, account pages, forms with personal data, and dashboards should not be treated like public articles. Requests with login cookies, POST methods, or specific parameters usually need to be excluded from the cache.
Nginx documentation explains the use of fastcgi_cache_bypass, fastcgi_no_cache, fastcgi_cache_valid, and fastcgi_cache_lock. The last feature is useful to prevent multiple concurrent requests from filling the same cache when items are not yet available. See Nginx FastCGI module documentation before enabling similar configurations in production.
For static assets like CSS, JavaScript, and images, use appropriate caching rules. Headers like Cache-Control, ETag, and Last-Modified help the browser determine whether resources can still be used or need to be revalidated. Good caching is not just about "storing everything for as long as possible," but rather a combination of cache age, invalidation strategies, and file naming that changes when the content changes.
If the server is fast, focus on the main resources
When TTFB is good but LCP is still slow, check the largest elements that are visible first. Often, this is the hero image, a title with a web font, or a content block that only appears after JavaScript finishes.
- Ensure the main image can be found directly from the HTML, not just inserted via JavaScript.
- Use image sizes appropriate for the display, then choose modern formats if compatible with the website's needs.
- Do not apply lazy loading to images that are immediately visible on the screen.
- Use
fetchpriority="high"selectively for main resources, not on many images at once. - Defer unnecessary third-party scripts for the initial view.
Preloading is also not a magic button. If too many resources are given high priority, the browser loses cues about what is truly important. Measure changes through the waterfall in DevTools and real user data if available.
What you can do now
- Test the same URL from consistent locations and devices.
- Record TTFB, LCP, page size, number of requests, and the largest resources.
- Compare cached pages with the first request without cache.
- If TTFB is high, check PHP-FPM, database queries, and external services.
- If TTFB is low but LCP is high, check the main images, fonts, CSS, and JavaScript.
- Change one thing at a time, then measure again before applying the next change.
In summary: performance optimization will be more effective if it starts from the location of the problem, not from a list of trendy tricks. A fast website is not the result of a single plugin or configuration, but rather the result of consistent measurements, caching with clear rules, reasonable queries, and important resources delivered at the right time.
Sources & further reading
- Optimize Largest Contentful Paint
- PHP: FastCGI Process Manager (FPM)
- PHP-FPM Configuration
- MySQL 8.4 EXPLAIN Statement
- Nginx FastCGI Module
- HTTP Caching
– Rio Yotto @rioyotto
