How do you optimize Craft CMS performance for high-traffic sites?

Tactics to harden Craft CMS performance for content-heavy, high-traffic websites without breaking authoring.
Learn a layered plan: caching, queries, queues, infra, and CDN—so Craft CMS performance scales predictably under load.

answer

Strong Craft CMS performance comes from layered caching, lean queries, and right-sized infra. Use template caching (partial + tag deps), element eager-loading, and static/edge caching via CDN. Move sessions/cache to Redis, tune PHP-FPM and DB, and offload image transforms to queue workers + ImageOptimize. Prefer Element API/GraphQL with pagination, add warmers to prebuild hot pages, and monitor with New Relic/Blackfire. Result: fast TTFB and stable p95 even on traffic spikes.

Long Answer

Scaling Craft CMS performance for high-traffic, content-dense sites means treating delivery like a relay team: CDN → web/PHP → Craft/Twig → database → assets/transforms. Each leg needs tuning and guardrails, or the baton gets dropped during a surge. Below is a pragmatic, battle-tested blueprint.

1) Cache hierarchy that never lies
Start at the edge. Put a CDN (Cloudflare/Fastly/Akamai) in front for static caching of HTML where possible (short TTL + soft purge), and always for assets. Use stale-while-revalidate so bursts serve warm pages while a background refresh rebuilds. In Craft, stack template caching granularly: cache partials (hero, nav, cards) with cache tags and smart keys (locale, section, user-group). Tie invalidation to element queries (tag deps) so publishing purges precisely. For authenticated areas, cache fragments around user-specific bits (ESI/edge side includes or client hydration). Add HTTP caching headers consistently (Cache-Control, ETag) to let browsers do free work.

2) Query discipline and eager-loading
Heavy pages choke on N+1 queries. Audit templates with the debug toolbar and Craft profiler; convert loops to eager-loaded relations (with / eagerLoadElements) and prune fields you don’t render. Favor IDs-first strategy: fetch IDs in a cheap query, then load detailed elements batched. Cap result sizes with pagination/infinite scroll; never pull 500 entries to render 20. For listings, pre-compute denormalized fields when editorial allows (e.g., store counts, dates, slugs) to avoid expensive joins.

3) Offload transforms and background work
Image transforms are CPU hogs. Enable asset volumes on S3/GCS with a CDN; use ImageOptimize (or modern AVIF/WEBP pipelines) and run transforms in queue workers (Supervisor/systemd) so requests don’t block. For bulk imports, feeds, or webhooks, push to queues and display progress in CP. Keep queues horizontal-scalable; prefer multiple small workers over one giant.

4) Fast PHP and predictable Twig
Tune PHP-FPM (pm static/dynamic, max children sized to CPU/RAM), turn on OPcache with sane memory, and eliminate Twig anti-patterns (string concatenation in loops, deep includes). Extract reusable blocks into cached partials. Replace expensive Twig filters with preprocessed fields or custom services where logic belongs. Keep environment config in config/project and per-env overrides tidy to prevent accidental debug modes in prod.

5) Robust storage & search
Move caches/sessions to Redis (low latency, shared across nodes). Use MySQL/PostgreSQL tuned for reads (buffer pool/shared buffers, connection pool via ProxySQL/pgBouncer). Add indexes for hot element queries (title, postDate, relation tables). For search at scale, delegate to Meilisearch/Algolia/Elasticsearch via plugins; keep Craft for authorship, not full-text heavy lifting. Schedule nightly analyze/vacuum (PG) or optimize (MySQL) for long-lived tables.

6) Delivery APIs that don’t over-fetch
If you power apps or sections via API, prefer Element API or GraphQL with tight schemas, field whitelists, and cursor-based pagination. Enable CDN caching for API GETs; tag headers for precise purge. Avoid unbounded filters that punch through caches; normalize parameters (e.g., map many filters to stable cache keys).

7) Prewarming and publishing flows
Warming prevents cold TTFB during the “battle for traffic.” On publish, enqueue a warmer to hit critical URLs (home, top categories, recent posts) and their variants (locale/device). At the edge, precompile critical HTML or prerender for SPA shells. For scheduled content, warm just before go-live. Keep purge lists small and targeted using element relationships.

8) Observability and budgets
Add APM (New Relic/Datadog) to trace PHP spans down to queries, and Blackfire to profile hot paths. Monitor p75/p95 TTFB, error rates, queue age, DB locks, and CDN hit rate. Establish performance budgets: “p95 TTFB ≤ 350 ms for cached pages; ≤ 800 ms for uncached.” Alert on drift, not just absolute failures, so slow creep doesn’t ambush you.

9) Hardening authoring without slowing FE
Large editorial teams can trigger cache storms. Batch invalidations, debounce publish-purges, and prioritize warms for public surfaces. Limit gigantic matrix fields on listing pages; fetch summaries, not everything. Offer preview isolation: previews bypass edge caches with Cache-Control: private, but still use fragment caches under the hood.

10) Infrastructure that scales horizontally
Run multiple PHP nodes behind a load balancer; keep app stateless (sessions/cache in Redis, assets in S3/GCS). Use rolling deploys with health checks. Pin Composer deps, enable OPcache preloading, and keep images/builds lean. DB: a primary with read replicas for reporting/API if needed (mind Craft write paths). Run regular disaster tests: fail cache, kill a worker, reboot DB replica—measure blast radius.

Stack these layers—edge/cache, queries, queues, infra, and APM—and Craft CMS performance holds steady under heavy content and heavier traffic.

Table

Layer Action Craft/Tooling Outcome
Edge cache CDN HTML/assets, SWR, precise purge Cloudflare/Fastly, surrogate keys Low TTFB, resilient spikes
Template cache Fragment/partial caching + deps {% cache %} with tag deps Fast renders, safe invalidation
Queries Eager-load, paginate, ID-first Debug toolbar, with, eagerLoadElements Fewer queries, no N+1
Media Queue transforms, modern formats Queues + ImageOptimize, S3+CDN Non-blocking images
Runtime PHP-FPM + OPcache tuned pm settings, OPcache/preload Stable p95 under load
Data Redis + DB tuning/indexes Redis, ProxySQL/pgBouncer Low latency, fewer locks
Search Offload full-text Meilisearch/Algolia/ES Faster queries, relevancy
APIs Cacheable reads, strict schemas Element API/GraphQL No over-fetch, easy purge
Observability APM + profiling New Relic/Datadog, Blackfire Bottlenecks visible

Common Mistakes

Relying on one big page cache and ignoring fragment caching—one edit nukes everything. Shipping N+1 queries by looping relations without eager-loading. Letting image transforms run inline on requests, causing timeouts. Treating Craft CMS performance as a server problem only: no OPcache tuning, tiny PHP-FPM pools, DB with missing indexes. CDN set to cache nothing (or everything) and purge all on publish—either useless or dangerous. GraphQL delivering unbounded fields, blowing caches. No APM/profiling—teams “guess and check.” Finally, storing sessions/files on a single node, blocking horizontal scale.

Sample Answers (Junior / Mid / Senior)

Junior:
“I enable fragment template caching and eager-load relationships to avoid N+1. I move images to S3 with a CDN and run transforms in the queue so pages don’t block.”

Mid:
“I layer edge caching (short-TTL HTML) with Craft fragment caches keyed by locale/section. Queries are profiled; I batch loads and paginate. Sessions/cache use Redis; PHP-FPM and OPcache are tuned. I add warmers on publish and monitor p95 with APM.”

Senior:
“I design a cache hierarchy: CDN SWR + surrogate keys, Craft fragment caches with tag-based invalidation, and API caching with stable keys. We offload search, run transforms asynchronously, and keep the app stateless to scale horizontally. DB is indexed for hot paths; we enforce budgets and trace slow spans with New Relic/Blackfire. This keeps Craft CMS performance predictable during peaks.”

Evaluation Criteria

Look for a layered strategy: CDN/edge + Craft fragment caches + DB/search tuning. Strong answers mention eager-loading, pagination, and queue-based transforms; moving cache/session to Redis; PHP-FPM/OPcache tuning; and stateless infra for horizontal scale. They describe precise invalidation (tag deps, surrogate keys), warmers, and observability (APM, profiling, budgets). Weak answers: “just add a CDN” or “bigger server.” The best responses tie Craft CMS performance to measurable p95/TTFB goals and show how publishing flows avoid cache storms.

Preparation Tips

Set up a staging site with a CDN. Enable fragment caching in Twig and measure before/after TTFB. Use the debug toolbar to find N+1s; fix via eager-loading. Move sessions/cache to Redis; tune PHP-FPM (pm, children) and OPcache. Put assets in S3 + CDN; run transforms in queues and try ImageOptimize with AVIF/WEBP. Add an API endpoint via Element API or GraphQL with pagination and cache headers. Create a publish hook that warms top URLs. Install New Relic/Blackfire; record profiles during a load test. Prepare a 60–90s story showing the layered gains in Craft CMS performance.

Real-world Context

A news site saw p95 TTFB jump during breaking stories. Profiling showed N+1 category queries and inline image transforms. Fixes: eager-load relations, queue transforms, add template caching on cards, and short-TTL CDN HTML with SWR. Publishing now purges only affected routes and triggers a warmer. Another retailer moved sessions/cache to Redis, tuned PHP-FPM, and added DB indexes for product filters; p95 fell from 1.8s to 450ms. A content hub limited GraphQL fields and cached GETs at the edge; bandwidth dropped 40% and Craft CMS performance stayed stable on launches.

Key Takeaways

  • Layer caches: CDN HTML/assets + Craft fragment caches with precise purges.
  • Kill N+1s with eager-loading; paginate and batch queries.
  • Offload images to queues + CDN; use modern formats.
  • Centralize sessions/cache in Redis; tune PHP-FPM/OPcache and DB.
  • Monitor p95/TTFB with APM; warm hot pages on publish.

Practice Exercise

Scenario: You’re handed a Craft site with 50k articles and global traffic. p95 TTFB is 1.6s during peaks, editors complain that publishing “freezes” pages, and image transforms time out.

Tasks:

  1. Add CDN with short-TTL HTML caching + stale-while-revalidate. Set surrogate keys by section/entry so purges are surgical.
  2. In Twig, wrap hero, nav, and list items in {% cache %} blocks with tag-based deps; key on locale and user-group.
  3. Use the debug toolbar to find N+1s; convert loops to eager-loaded relations and cap queries with pagination.
  4. Move sessions/cache to Redis; tune PHP-FPM (pm, max_children) and enable OPcache with preloading.
  5. Shift assets to S3 + CDN; enable queue workers for image transforms and implement ImageOptimize (AVIF/WEBP).
  6. Add a publish hook that warms top 50 URLs and their locale variants.
  7. Create dashboards (New Relic/Datadog): p75/p95 TTFB, queue age, DB slow queries, CDN hit rate. Set alerts on budget breaches (p95 > 600ms).
  8. Write a 60–90s narrative explaining measured before/after gains, the cache hierarchy, and why the system now rides traffic spikes without melting.

Still got questions?

Privacy Preferences

Essential cookies
Required
Marketing cookies
Personalization cookies
Analytics cookies
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.