Runbook: slow site
For on-call staff and administrators when a customer reports that their Exponential site is slow. The levers and their order come from 10.12 Performance tuning.
Contents
Measure first
A slow site is a number, not a feeling. Measure the front page and the pages the customer named:
curl -s -o /dev/null -w '%{http_code} %{time_total}\n' https://www.example.com/
php bin/php/console exp:benchmark --allow-root-userexp:benchmark requests the front page, the first menu pages, the search page and the admin login page, 100 times
each after 5 warm-up requests, 4 at a time, and prints median, p90 and p99. If you saved a baseline when the site was
healthy, compare with it:
php bin/php/console exp:benchmark --baseline=var/benchmark/base.json --threshold=15 --allow-root-userWarning
A benchmark is load. On a site that is already struggling, run a small one (--requests=20) or test from a
staging copy. Details: Benchmarking a hosted site.
Is it everyone, or one page?
| Pattern | Likely cause |
|---|---|
| Every page slow, also cached ones | the machine: CPU, memory, swap, disk; or the engine has no free worker |
| Only the first request after a change is slow | caches being rebuilt after a clear or a deploy: normal, warm them |
| Only signed-in users (editors) are slow | they bypass the anonymous caches; check the database and OPcache |
| One page slow | that page's template or fetch; check its log lines |
| Slow at certain times | a cron job, a backup, an import, or a crawler; check the request logs and cron times (slow under crawlers) |
Checks in order
- The machine:
uptime,free -m,df -h,iostatif available. Swap use or a full disk explain everything else (Disk full). - The caches:
php bin/php/cache.php status --json --allow-root-usershould end inPASS. Check that[ContentSettings] ViewCaching=enabledand[TemplateSettings] TemplateCache=enabled(both shipped) were not switched off in an override. - The engine:
- Velocity:
php bin/php/console exp:velocity status --allow-root-user(workers as configured);exp:velocity cache statsfor the response cache. - PHP-FPM: are all children busy? See PHP-FPM tuning.
- Velocity:
- OPcache:
php bin/php/cache.php opcache status --allow-root-user. Never run an Exponential site withopcache.validate_timestamps=0; under Velocity the shipped settings matter (OPcache). - The database: on SQLite after an install or a large import, "the query planner has no statistics": run
sqlite3 var/storage/sqlite3/exponential.db ANALYZEonce (12.10). On MongoDB, check that the indexes frombin/mongodb/create_indexes.jsexist. - Traffic:
security.logand404.logshow bursts of probes and not-found requests; a crawler or an attack can look like slowness (Request logs). - The static cache: it should be off (
[ContentSettings] StaticCache=disabled, the shipped value). With it on, every publish regenerates affected pages inside the editor's request (Static cache).
Fixes in order of effect
From 10.12:
- Serve with Velocity and its response cache; persistent workers keep the application warm.
- Keep the content view cache and template-block cache on, and warm the site after a clear:
php bin/php/warm.php --allow-root-user. - OPcache, configured as OPcache describes.
- APCu for Velocity's response cache memory tier and the SQL query cache in
sharedmode. - The SQL query cache, measured on the site.
- The database tuning of chapter 9.
Change one lever at a time and measure again with the baseline.
Slow under crawlers and bots
When the slow times match bursts of crawlers (search engines, AI crawlers, SEO tools; the access log shows many anonymous requests from a few user agents within a minute), the cheapest fix is a cache that answers anonymous pages without PHP:
- Velocity: its response cache does this out of the box.
- LiteSpeed or OpenLiteSpeed: the server's page cache, switched on with the commented block of
.htaccess_rootor the same rules in the virtual host, no plugin needed: Page caching with LiteSpeed and OpenLiteSpeed. - Apache or nginx: cacheable headers for anonymous visitors and a caching proxy in front (HTTP cache headers, Varnish and reverse proxies), or the role-aware HTTP cache.
robots.txt can slow polite crawlers down; it does nothing against the others.
After the incident
- To do: Save a new baseline when the site is healthy again:
php bin/php/console exp:benchmark --save=var/benchmark/base.json --allow-root-user. - To do: Note the cause and the fix in the site's record.
- To do: If the site has outgrown its plan, tell the customer with the measurements (Designing hosting plans).
Related pages
- Caching layers
- Cache clearing and warming
- Benchmarking Exponential
- Free documentation: this page is free to copy, translate and improve under the GNU FDL 1.3.
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.