Most slow Drupal sites aren’t slow everywhere — they’re slow in one or two places that touch every page. The fix is rarely a bigger server; it’s finding those places. You’ll need drush, SSH access, and a site you can safely poke at. It helps to already be comfortable clearing caches the right way, because you’ll be doing it between changes.
Measure before you touch anything
Pick three real pages — the front page, a busy landing page, and something content-heavy — and time them from the command line:
curl -s -o /dev/null -w "total: %{time_total}s\n" https://example.com/
Run each one twice and write down both numbers. The second run is your warm number; the gap between them is your caching story. No numbers, no way to know later whether you actually helped.
Confirm page caching is on and actually working
Drupal ships two big caches: Page Cache (whole pages for anonymous visitors) and Dynamic Page Cache (most of the page for everyone else). Check that pages are actually hitting them:
curl -sI https://example.com/ | grep -i x-drupal
You want X-Drupal-Cache: HIT on the second request. A steady MISS or
UNCACHEABLE means something on the page is spoiling it — hold that thought
for step five. Also make sure anonymous pages are allowed to live a while:
drush config:get system.performance cache.page.max_age
drush config:set system.performance cache.page.max_age 3600 -y
Turn on CSS and JS aggregation
Aggregation bundles dozens of small CSS and JS files into a few, which saves the browser a pile of round trips. It’s on by default in newer installs, but sites that grew up through upgrades — or had it switched off for debugging — often ship bare:
drush config:set system.performance css.preprocess 1 -y
drush config:set system.performance js.preprocess 1 -y
drush cache:rebuild
Find the slow queries and heavy views
Views are the usual suspects: a listing with no cache set will run its query on every single load. In each slow view, open Advanced → Caching and set time-based caching — even one minute helps a busy page enormously. To catch the rest, install Webprofiler on a local copy only — it puts a toolbar on every page showing exactly where the milliseconds went: which queries ran, what missed cache, what rendered slow. Fix the top item, re-measure, repeat. Resist fixing the fifth-worst thing first; the portage doesn’t get shorter by rearranging the packs.
Deal with the uncacheable bits
One personalized block — a cart, a “hello, name,” a random promo — can mark a
whole page uncacheable and drag everyone down with it. If step two showed
UNCACHEABLE, hunt for the block or module setting a max-age of zero. The
usual fixes: cache the block with the right context, load the personal bit
with JavaScript after the page lands, or ask honestly whether the page needs
it at all. Anything session-related (an unneeded cookie set for every
visitor) has the same effect and is worth chasing down.
Measure again and keep the receipt
Run the same curl timings on the same three pages and compare against your step-one numbers. Cold and warm should both be down, and the warm loads on anonymous pages should be well under a second. Save both sets of numbers somewhere durable — when someone asks in six months whether the site got slower, you’ll have a true north to check against instead of a feeling.