Automated WordPress Optimization: The Change Is Easy, The Measurement Is Not.
Three of my own performance measurements returned confident wrong answers in one afternoon. Here is what that means for automating the fixes.
Short answer: the hard part of automated WordPress optimization is not applying the fix. It is being sure the measurement that triggered it was true. In one afternoon of building checks for this post, three of my own measurements returned confident, plausible, completely wrong answers.
Any one of them would have become an automated action if something had been wired to act on it.
What is actually left to automate
I measured four of our own sites from outside, looking at the three optimizations that get recommended most often and are genuinely mechanical: compression, static asset cache headers, and image format.

done everywhere.*
| Site | TTFB | HTML | Compression | Images | Formats | WebP/AVIF |
|---|---|---|---|---|---|---|
| posimyth.com | 786ms | 19,888B | brotli | 18 | 16 PNG, 2 JPG | 0 |
| theplusaddons.com | 1,545ms | 108,508B | brotli | 64 | 61 PNG, 3 JPG | 0 |
| nexterwp.com | 855ms | 66,358B | brotli | 49 | 45 PNG, 4 JPG | 0 |
| uichemy.com | 1,525ms | 133,625B | brotli | 7 | 7 PNG | 0 |
That result is not what I expected and it changed what this post is about. Compression was already brotli on all four. Static assets already carried a thirty day max-age. The infrastructure-level optimizations, the ones an agent could flip without touching content, were finished before I got there.
What is left is image format, and the WebP and AVIF numbers I measured earlier say the saving is real. So why has nobody automated it?
Because converting the file is safe and changing what the page serves is not. Those are two different operations and only the first one is reversible by whatever did it.
I made the exact same measurement error again, re-checking this post
Re-running the four-site image audit while expanding this post, I reproduced the first of the three wrong answers below, by accident, before I had even reached that section again.
curl -s -H "Accept-Encoding: br,gzip" -D - -o body.html "https://example.com/"
Every one of the four sites reported zero images. Brotli content-encoding, confirmed in the headers, and a file full of raw compressed bytes on disk because I had set the Accept-Encoding request header manually without telling curl to actually decompress the response. file body.html called it “data.” The first hundred bytes were unreadable binary. Exactly the failure this post’s own table already names, reproduced live, by the same author, while specifically checking whether it still held.
The fix is one flag, --compressed, which tells curl to negotiate and decode automatically rather than pass the raw encoded bytes through:
curl -s --compressed -D - -o body.html "https://example.com/"
| Site | Images (corrected) | WebP / AVIF |
|---|---|---|
| posimyth.com | 18 | 0 |
| theplusaddons.com | 64 | 0 |
| nexterwp.com | 86 | 0 |
| uichemy.com | 51 | 51 |
Three of the four match or exceed the original counts, unremarkable drift as pages gained images over time. The fourth is the actual finding: uichemy.com, which had zero images in a modern format in the original audit, now serves all fifty-one of its images as WebP or AVIF. That gap has been closed completely on one of the four sites this post uses as its running example, unprompted by anything in this post specifically, and it is worth reporting as directly as the three wrong answers below are, because a re-check that only ever surfaces mistakes and never surfaces genuine progress is telling half the story.
Reversibility is the property that matters
I have come round to sorting optimizations by whether the thing that made the change can undo it, rather than by how many milliseconds they save.

practice to belong in ask-first rather than auto-run.*
| Optimization | Touches | Safe unattended? | Why |
|---|---|---|---|
| compression on/off | response header | yes | fully reversible, no content change |
| static asset cache headers | response header | yes | worst case, one stale asset for one max-age |
| object cache on/off | server config | with a rollback | a bad backend takes the site down |
| image format conversion | rewrites what the page serves | not alone | conversion is safe, swapping URLs is not |
| removing an unused plugin | deletes code and data | no | nothing here is reversible by the agent |
This maps cleanly onto the three-tier permission model I keep returning to. Top two rows run on a schedule. Middle row proposes and waits. Bottom row does not happen without a person.
Note that saving has almost nothing to do with the ordering. The bottom row would often be the biggest win on the page.
The three wrong answers
Here is the part I would rather not write and think is the most useful thing in this post.

| What the check reported | Why it was wrong | Actually happened |
|---|---|---|
| the page had zero images | fetched Accept-Encoding: gzip, br, decoded gzip only | silently read a brotli body as text |
| the cache plugin caches nothing | the cache directory had been deleted by hand | plugin couldn’t recreate it, wrote nothing |
| cart contents leaked between visitors | substring match on a product name | matched a product grid, not the cart |
Take the first one. My audit asked for Accept-Encoding: gzip, br, got brotli back, and decoded it as gzip. No exception, no warning, just bytes that did not parse as HTML. The result was a clean report saying four sites had zero images on them. An optimizer reading that would have concluded there was nothing to do, and it would have been wrong about 138 files.
The second one is worse in a way, because I caused it. I deleted a cache directory by hand while testing, the plugin could not recreate it, and every subsequent measurement said caching was not working. That was true of my broken rig and false of the plugin. I threw the measurement away rather than publish it, which is the only correct move and it cost me most of an hour.
The third one nearly made it into a different post. A substring check for a product name on the cart page said a second visitor was seeing the first visitor’s cart. It was a product grid block. The Store API cart for that second session was empty. Had I not checked, I would have told people their carts were leaking.
What I would put around anything that runs unattended

triggered the change.*
The first rule is the cheap one and it caught two of my three errors. If a check says a page has zero images, ask a second question that should agree, and be suspicious when it does not. Zero is suspicious in a way that “fourteen” never is.
The third rule is the one people skip. Optimizers report what they did, not what changed. “Converted 138 images” is an activity log. “TTFB went from 1,545ms to 1,102ms” is a result, and only the second one tells you whether to keep the change.
The fourth rule sounds like bookkeeping and is not. Across a client portfolio, the question you get asked is never “what did you optimize”, it is “is the site faster than when we started”. Without stored measurements that question has no answer, and you end up defending work with adjectives.
Worth saying plainly: a before-and-after on a single page load is close to meaningless. I requested two of those sites five times each, back to back, changing nothing:
theplusaddons.com 549ms 795ms 273ms 415ms 476ms (fastest to slowest: 2.9x)
uichemy.com 948ms 585ms 542ms 1011ms 792ms (1.9x)
Same URL, same minute, nothing altered. The slowest sample is nearly three times the fastest. An optimizer that measures once, changes something, measures once again and reports an improvement is reporting the weather.
Take several samples, use the median, and be honest that anything under roughly a 20% shift on that median is not a result you can defend to a client who asks.
I ran the same five-sample check again on both sites while expanding this post, changing nothing on either one:
theplusaddons.com 270ms 487ms 479ms 444ms 441ms (fastest to slowest: 1.8x)
uichemy.com 909ms 1433ms 1038ms 919ms 1011ms (1.58x)
Different absolute numbers from the original reading, expected on a different day against different network conditions, and the same underlying shape: a real, non-trivial spread between the fastest and slowest sample of the same URL, requested seconds apart, with nothing changed in between. theplusaddons.com‘s spread narrowed from 2.9x to 1.8x and uichemy.com‘s from 1.9x to 1.58x, both still comfortably large enough to swallow a genuine, real optimization if measured with a single before-and-after pair rather than several samples compared by median. That consistency, real variance on every measurement attempt regardless of which day it is run, is the actual argument for always sampling more than once, demonstrated a second time rather than asserted from a single afternoon’s data.
Where this leaves automated WordPress optimization
I am not arguing against automating this work. I am arguing that the automation belongs around the measurement first and the change second, which is the opposite of how most of these tools are built.
Protuno’s performance agent, Dash, carries 16 playbooks, and the ones that run unattended are all in the top two rows of that reversibility table. The image work proposes and waits. That is not caution for its own sake, it is the same conclusion I reached this afternoon by getting three answers wrong in a row. Straight with you as on every post here: Dash is built and named but not live yet.
If you take one thing from this: before you automate a fix, automate a second, independent way of checking that the problem is real. It is less work than the fix and it is worth more.
What re-checking this specific post taught me about my own habits
Catching myself reproduce the exact brotli-decoding mistake this post already documents, while specifically re-verifying the section that documents it, is worth sitting with rather than treating as a minor embarrassment to smooth over. I knew the failure mode. I had written it down, publicly, with the specific Accept-Encoding header that triggers it. And I typed the same broken command again anyway, because the mistake is not a gap in knowledge, it is a habit in how a quick curl command gets written under no particular pressure, and knowing about a failure mode in the abstract does not automatically install the habit that avoids it.
That is a more useful data point for this post’s argument than a clean re-run would have been. A written rule, “remember to decode brotli correctly,” is exactly the kind of policy-level guard this post’s reversibility table already ranks below a structural one. The structural fix is not remembering. It is a script that always includes --compressed, checked into whatever tooling runs these audits, so the correct behaviour does not depend on any individual person recalling a lesson from a blog post they wrote themselves. The same argument this post makes about automating changes, prefer a guarantee to an instruction, applies just as much to the measurement tooling as to the WordPress site being measured, and I would not have noticed that symmetry without making the mistake a second time while writing about the first one.
The same problem at production scale
Covers measuring an agent’s false-positive rate per surface as a first-class metric, not an afterthought, the same instinct behind this post’s second-measurement requirement.
Comments