Websites often feel normal when tested alone, then start to slow down when visitors arrive simultaneously. In such cases, adding RAM or upgrading the hosting plan may help, but it doesn't necessarily solve the problem. The bottleneck could be due to oversized images, heavy JavaScript, PHP-FPM queues, or MySQL queries reading too many rows.
A safer approach is to check the request path from front to back. Think of a website like a restaurant: customers see the wait time at the table, but the cause could be a menu that takes too long to prepare, a crowded kitchen, or a cashier with only one staff member.
Start with the symptoms visible to users
The first step is not to open the server configuration, but to determine which part feels slow. Does the old page display the main content? Does the new button respond after a few seconds? Or has the page opened, but the layout shifts and isn't stable yet?
Core Web Vitals help differentiate these three experiences. Largest Contentful Paint or LCP measures when the main content is visible. Interaction to Next Paint or INP describes how quickly the page responds to interactions. Meanwhile, Cumulative Layout Shift or CLS measures unexpected element shifts. The common target for a “good” score is an LCP of 2.5 seconds or less, an INP of 200 milliseconds or less, and a CLS of 0.1 or less at the 75th percentile of visits.
These numbers are not a definitive verdict. Testing from a laptop with a fast connection may look good, while mobile users on cellular networks experience different conditions. Therefore, compare laboratory test results with real user data when available.
Check the waterfall before changing configurations
The Network panel in the browser shows the sequence of requests that occur when the page is opened. Pay attention to the following three points:
- Document request: if the request for the main document takes a long time before the first byte is received, the problem likely lies with the server, application, or database.
- Static files: CSS, JavaScript, fonts, and large images can slow down the page after the server has actually responded.
- Chained requests: a single JavaScript file waiting for another file or too many third-party connections can delay rendering and interaction.
Use a slower network mode and a CPU-limited device when testing. The goal is not to make the results look bad, but to approximate real user conditions. Save results before and after changes so we don't rely solely on feelings.
If the document request is slow, check the server path
The response time of the main document is often referred to as TTFB, which is the time until the browser receives the first byte. However, a high TTFB is just a symptom, not a diagnosis. In PHP applications, common causes include exhausted PHP-FPM processes, scripts waiting for external services, or slow database queries.
PHP-FPM uses workers to handle requests. If all workers are busy, new requests must wait. Check the number of active processes, requests being processed, and the slow request log if available. PHP-FPM provides a slowlog feature to help identify scripts that run too long, and the pool status can be used to monitor worker conditions.
Do not immediately increase the number of workers to the maximum. Each worker consumes memory. If RAM runs out and the system starts swapping, overall performance can deteriorate. It’s better to record memory usage per process, set a reasonable limit, and then test under increased load.
Differentiate PHP queues from inherently slow applications
If the wait time occurs before PHP starts working, the pool may be out of workers. If workers are available but requests remain slow, investigate the application code and the services being called. For example, a product page may look simple, but it could still be making requests to stock APIs, recommendation systems, or shipping services before sending HTML.
For processes that do not need to be completed before the user sees the page, consider job queues. Sending emails, creating thumbnails, or generating reports typically do not need to finish before the main page displays.
Investigate MySQL with EXPLAIN, not guesses
Databases are often blamed as the source of slowness, but the same query can be fast on a small table and become heavy as data grows. Start with the truly slow queries, then run EXPLAIN to see the execution plan.
EXPLAIN SELECT id, title
FROM articles
WHERE category_id = 7
ORDER BY published_at DESC
LIMIT 20;Check if the columns used for filtering, sorting, or joining tables have the appropriate indexes. MySQL itself recommends using indexes and EXPLAIN to understand how queries are executed. However, blindly adding indexes also has costs: storage space increases and write operations can become heavier.
Also, pay attention to queries that retrieve too many columns or rows. For list pages, avoid SELECT * if only a few columns are displayed. Limit results, use appropriate pagination, and ensure queries do not perform full table scans on large tables without reason.
Cache helps, but it’s not a problem solver
Caching can reduce repetitive work. The same page does not need to be rendered from scratch for each visitor if its content does not change every second. Object caching can also reduce repeated queries to the database.
However, overly aggressive caching can serve stale data, making changes not immediately visible, or store pages that should be personalized. First, determine what is safe to cache: public pages, stable query results, or static assets with versioned filenames. Do not assume all responses can be cached just because caching improves testing scores.
What can be done now
- Test slow pages with browser DevTools and save their waterfall.
- Note whether delays occur on the document request, static assets, or interactions after the page opens.
- Check the status and slowlog of PHP-FPM before changing the number of workers.
- Take the slowest queries from the application or database logs, then analyze them with
EXPLAIN. - Measure CPU, RAM, disk I/O, and database connections when traffic increases.
- Test one change at a time, then compare the results with the initial conditions.
In summary, a slow website is not a single type of problem. If we immediately replace the server without knowing the bottleneck, costs can rise while complaints persist. Sequential diagnosis—from user experience, waterfall, PHP-FPM, to MySQL—makes tuning more measurable and reduces risks.
Sources & further reading
- Web Vitals – web.dev
- How the Core Web Vitals metrics thresholds were defined – web.dev
- PHP: FastCGI Process Manager (FPM) – PHP Manual
- MySQL 8.4 Reference Manual – Optimization
- MySQL 8.4 Reference Manual – Optimizing SELECT Statements
– Rio Yotto @rioyotto
