Performance auditing

Why Is This Site Slow?

Microscope audits your PHP runtime, database, server, Craft configuration, content schema and Twig templates — then tells you, for every single finding, exactly what to change and how to check it worked.

Microscope

See It in Action

A score you can track, an area breakdown that says where to look, and findings that carry the fix with them.

Scan #142

4 August 2026, 03:00 · scheduled · SAPI fpm-fcgi · 31 checks, 2 skipped

Print report Download PDF
68 grade C
PHP
52/100
3 to look at
Database
74/100
2 to look at
Server
90/100
1 to look at
Craft
61/100
3 to look at
Content schema
100/100
All clear
Templates
43/100
4 to look at
critical
OPcache is disabled
PHP is recompiling every file on every request.
Found
off
Recommended
on
warning
InnoDB buffer pool is smaller than the database
Craft’s data is 1.4 GB against a 128 MB pool.
Found
128 MB
Recommended
≥ 1.75 GB
warning
Element queries inside a loop
4 templates run a query once per row.
Found
4 templates
Recommended
eager-loaded
critical
Dev mode is on in production
Craft is recompiling templates and writing a debug log on every request.
Found
devMode: true
Recommended
devMode: false

Why it matters

With devMode on, Craft checks every template for changes and recompiles the ones that moved, skips several caches entirely, and writes a verbose log line for every query. On a production site that is the single most expensive setting you can leave switched on — and it exposes stack traces to visitors when something throws.

How to fix it

Set CRAFT_DEV_MODE=false in this environment’s .env, or pin it per-environment in config/general.php:

return GeneralConfig::create()
    ->devMode(App::env('CRAFT_DEV_MODE') ?? false)
    ->allowAdminChanges(App::env('CRAFT_ALLOW_ADMIN_CHANGES') ?? false);

Then clear the compiled templates so the next request builds them fresh: php craft clear-caches/compiled-templates.

How to check it worked

Run php craft microscope/scan/run --category=craft from the server, or re-scan from the control panel. This finding should come back as a pass.

skipped
System resources
This host doesn’t allow shell_exec, so CPU count and load average couldn’t be read. Not counted against the score.

A representation of the scan report screen. Figures are illustrative.

Features

A performance audit that ends with instructions, not a list of complaints.

Every Finding Comes With the Fix

Most performance advice for Craft is generic — “enable OPcache”, “use eager loading”. Microscope tells you whether this server has OPcache on, how big it is, whether it’s full, and the exact php.ini lines to change. Every finding carries what was measured, what it should be, why it costs something, the steps to fix it with config snippets, and how to confirm the fix took.

Six Areas, One Report

The PHP runtime, the database, the server, Craft’s own configuration, your content schema and your Twig templates. Twenty-seven checks, each of which can be switched off from Microscope → Checks when it doesn’t apply to how your site is hosted.

It Reads Your Templates

Static analysis of every .twig file for element queries inside loops, relational fields executed per row, unbounded .all(), .all()|length where .count() belongs, transforms generated in loops, query-heavy templates with no {% cache %}, includes without only, and images missing dimensions or loading hints. Eight rules, each reporting the file and the line.

A Score That Can’t Be Padded

Each area starts at 100 and loses the cost of what’s wrong — a critical costs 25, a warning 10, a notice 3 — so adding passing checks can never inflate a score. Checks that couldn’t run are recorded as skipped and excluded entirely: a Postgres site, or a host that blocks shell_exec, isn’t marked down for it.

Never in the Request Path

Microscope is not a profiler and adds nothing to your front end. It runs on demand or on a schedule, writes a report, and gets out of the way. Nothing is injected into a page, nothing is measured per request, and there is no agent to keep running.

A Report You Can Hand Over

Two outputs from the same document: a print-optimised page the browser saves as a PDF with better typography than any library produces, and a real PDF file rendered server-side for emailing, attaching or archiving. Server-side PDF is an optional dependency, so a strict Composer audit policy can never block the install.

Scheduled, and Emailed Only When It Matters

Daily, weekly or monthly, started from cron or piggybacked on control panel requests for hosting without usable cron. The default notification is to send only when something new appears — a report that arrives unchanged every week stops being read.

A Gate for Your Deploy Pipeline

php craft microscope/scan/run --fail-on=critical exits non-zero when something critical is found. A build can refuse to ship the change that turns OPcache off or leaves devMode on.

Your Checks, In the Same Report

Register your own checks through one event. A plugin knows better than Microscope does what “misconfigured” looks like for it, and its findings belong in the same report, scored the same way and printed on the same page.

What It Looks At

Twenty-seven checks in six areas, plus eight template rules. Any of them can be switched off when it doesn’t apply to your hosting.

PHP

6

The runtime the site is actually served by.

  • OPcache
    Enabled, sized, hit rate, restarts, timestamp validation.
  • PHP version
    Against the version Craft performs best on, and PHP’s own support schedule.
  • Memory limit
    Against what image transforms, project config and large queries need.
  • Request limits
    Execution time, max_input_vars and upload limits.
  • Performance extensions
    APCu, Redis, Imagick, intl and Zstandard.
  • Realpath cache
    Size and lifetime — Composer applications lean on it heavily.

Database

7

MySQL and MariaDB, measured against your real data.

  • InnoDB buffer pool
    Against the actual size of Craft’s data, plus disk read rate.
  • Database version
    Against the version Craft is tuned for.
  • Table sizes
    Craft tables that have grown out of proportion to the content.
  • Temporary tables
    How often sorts and groups spill to disk.
  • Slow query log
    Whether it’s on, and how many the server has seen.
  • Connections
    Connection limits, refusals, and the thread and table caches.
  • Query cache
    MariaDB’s query cache, which serialises writes behind a global lock.

Server

4

The machine and the connection in front of it.

  • System resources
    CPU count, available memory and load per core.
  • Response compression
    Whether HTML is gzip- or Brotli-compressed over the wire.
  • HTTP protocol
    HTTP version and TLS.
  • Disk space
    Free space on the volume holding the site.

Craft

6

Craft’s own configuration, read from the running install.

  • Dev mode
    Dev mode, admin changes and template debugging left on in production.
  • Caching
    Cache driver, template caching and session storage.
  • Queue
    How background jobs are run, and whether a backlog has built up.
  • Image handling
    Image driver, when transforms are generated, and optimisation.
  • Garbage collection
    Revision limits, soft-deleted content and whether anything cleans up.
  • Project config
    Its size, and whether Craft is watching it for changes in production.

Content schema

3

The shape of the content, not the content itself.

  • Field layouts
    Layouts wide enough to slow the entry editor — or overflow max_input_vars and silently lose content on save.
  • Field usage
    Custom field count, and fields not used in a single layout.
  • Content volume
    How much content the site holds, and where unbounded queries stop being survivable.

Templates

8

Static analysis of every Twig file — eight rules.

  • Queries in loops
    An element query executed once per row.
  • Relations in loops
    A relational field resolved per row instead of eager-loaded.
  • Unbounded queries
    .all() with no limit.
  • Count via all
    .all()|length where .count() belongs.
  • Transforms in loops
    Image transforms generated per iteration.
  • Missing cache tag
    Query-heavy templates with no {% cache %}.
  • Includes in loops
    {% include %} without only, inside a loop.
  • Image dimensions
    Images missing width, height or a loading hint.

Plugins can register their own checks, which appear in the same report and are scored the same way.

Frequently Asked Questions

Common questions about Microscope.

Nothing. Microscope is free, under the Craft license. There is no paid edition, no trial and no upsell — every check, both report formats, scheduled scans and the console commands are all in the one free plugin.

No, and no. Microscope never sits in the request path: nothing is injected into your pages, nothing is measured per request, and there is no agent left running. A scan runs on demand or on a schedule, writes a report, and stops.

Craft CMS 5.3 or later and PHP 8.2 or later. The database checks need MySQL or MariaDB — on PostgreSQL that whole area records itself as skipped rather than guessing.

Each area starts at 100 and loses the cost of each problem found: a critical costs 25, a warning 10 and a notice 3. The overall score is the mean of the areas that actually ran. Subtracting rather than averaging means adding passing checks can never inflate a score.

They are recorded as skipped, with the reason, and excluded from the score entirely. A shared host that will not report its innodb_buffer_pool_size has not got a small buffer pool — it has an unknown one, and scoring it down would be making something up.

No — you can start a scan from the control panel, and that is actually the more accurate way to run it for the PHP area. See the next question.

Because they are reading different PHP configurations. The CLI SAPI routinely differs from the one serving the site — different memory_limit, different max_execution_time, and very often no OPcache at all. Microscope records which SAPI each scan came from and the PHP findings say so, but for the PHP area specifically a scan started from the control panel describes the site more accurately.

Only for the server-rendered PDF, and it is deliberately optional. Every published dompdf release carries a security advisory, and Composer’s audit policy blocks flagged packages by default — requiring it would make Microscope uninstallable on any site with that policy on. Without dompdf everything still works and the PDF route redirects to the printable page. Both dompdf 2.x and 3.x are supported, so a site that already has it via Commerce, Formie or Freeform needs no extra install.

It reads your .twig files as text and applies eight structural rules — it never executes a template or renders a page. Containment is tracked by byte offset rather than by line, so a one-line {% for %} with a query inside it is caught like any other. Findings name the template and the line.

Yes. php craft microscope/scan/run --fail-on=critical exits non-zero when a critical finding is present, so a build can refuse to ship the change that turns OPcache off or leaves devMode on. --fail-on=warning is stricter still.

Two ways, chosen in settings. Cron is recommended: run php craft microscope/scan/scheduled hourly and it starts a scan only when one is due. For hosting without usable cron, control panel requests can trigger it — Microscope checks after the response has been sent, so nobody waits for it, and pushes the scan onto the queue behind a lock so concurrent requests cannot start several.

Not by default. The default is to notify only when something is new or has got worse, because a report that arrives unchanged every week stops being read. You can switch it to every scan, or to any scan with problems, and optionally attach the PDF.

No. The schema checks count fields, layouts and totals; they never read field values. The template rules read template source, not rendered output. Nothing leaves your server — there is no external service involved at any point.

Yes, through Checks::EVENT_REGISTER_CHECKS. A check describes itself, says whether it can run in this environment, and returns findings; BaseCheck gives you critical(), warning(), notice() and pass(). Your findings are scored, reported and printed exactly like the built-in ones. See Extending.

Put your site on the slide

One command, and you know what to fix first.

Free, under the Craft license · Craft 5.3+ · PHP 8.2+