WordPress Statistics For Operations: The State Of Things, September 2026.

57.6% of installs are on unsupported PHP by New Year’s Day. Plus what we found auditing our own portfolio, including the parts that failed.

Aditya Sharma·9 min read

Short answer: the headline wordpress statistics for operational health are worse than the ecosystem’s reputation suggests. 38.8% of installs run a PHP branch with no security fixes, and another 24.8% lose support on 31 December, taking the total to 57.6%.

This is the first of these I have written. Everything below was measured rather than cited from somebody else’s report, and the second half is our own portfolio, including the parts that failed.

The ecosystem-wide WordPress statistics

Table of ecosystem measures. 38.8 percent of installs run an unsupported PHP branch, 24.8 percent are on PHP 8.2 losing support on 31 December, totalling 57.6 percent. 17.2 percent run PHP 7.4, 24.2 percent run WordPress older than 6.9, and 1,614 plugins and themes were removed from the directory in 2024
From the WordPress.org statistics API and php.net’s own supported-versions page, read

4 to 7 September 2026.*

MeasureValueSource
installs on a PHP branch with no security fixes38.8%api.wordpress.org + php.net
installs on PHP 8.2, security support ends 31 Dec 202624.8%same
unsupported today or by New Year’s Day57.6%sum of the above
installs on PHP 7.4, unsupported since 202217.2%api.wordpress.org
installs running WordPress older than 6.924.2%api.wordpress.org
plugins/themes removed from the directory in 20241,614Patchstack

The third row is the one worth putting in front of a client. On current numbers, on New Year’s Day, roughly six in ten WordPress installs will be running PHP that nobody patches. That date was published years in advance and it is not moving.

I have written separately about how to have that conversation without predicting a breach, because overstating it is how the advice gets discounted.

Our own portfolio

A report about other people’s sites would be worth less, so here is ours.

Table of audit results across seven POSIMYTH domains and four production sites. All six domains use a single DNS provider and none publish DNSSEC. All seven have DMARC but only three enforce on all mail, and one enforces with no SPF record. Two of four sites expose ten usernames each via the REST API. Two publish oversized llms.txt files
Publishing your own audit is uncomfortable and it is the only version of this worth

reading. Every red row is now on a list with an owner.*

CheckResultReading
domains on a single DNS provider6 of 6one failure domain
domains publishing DNSSEC0 of 6none
domains publishing a CAA record1 of 6and that one is incomplete
domains with a DMARC policy7 of 7all
DMARC policies enforcing on all mail3 of 7two at pct=5, one at p=none
domains enforcing DMARC with no SPF1a real gap
sites exposing usernames via REST2 of 410 usernames each
sites blocking xmlrpc.php3 of 4majority
sites publishing an oversized llms.txt2119KB and 212KB
certificates expiring within 90 days6 of 6normal, all auto-renewing

Three findings I did not expect.

Two of our sites expose ten usernames each through the REST API. Not a vulnerability, and half of what a credential attack needs.

Two of our DMARC policies sit at pct=5, which applies the policy to five percent of failing mail and treats the rest as though there were no policy. That is a legitimate rollout step we stopped on and forgot.

One domain publishes a p=quarantine DMARC policy with no SPF record at all, leaving DKIM as the only thing alignment can use.

What measuring taught me about measuring

The most useful thing to come out of this was not any single number.

Table of three measurement traps: counting autoloaded options with autoload equals yes returns NULL, counting images missing alt text returns 17,261 false positives, and counting WooCommerce orders in wp_posts returns ten drafts. Each with the correct approach
All three share a shape. A query that used to be correct still runs without error and now

returns something meaningless.*

TrapWrong answerCorrect approach
counting autoloaded options with autoload='yes'NULL, reads as a clean databasematch on, auto and legacy yes
counting images missing alt text17,261 across 45 pagesexclude data: URI stubs; real answer: 0
counting WooCommerce orders in wp_posts10 rows, all “draft”read wp_wc_orders under HPOS

The alt-text one nearly went out as a headline. A crawl reported 17,261 images missing alt attributes across 45 pages. Every single one was a lazy-loading placeholder, and the real count was zero.

The lesson generalises well beyond these three: if a check has been reporting good news for a long time, the most likely explanation is that it stopped measuring anything.

What we are changing as a result

A report that produces no work is entertainment, so here is what came out of ours.

The two sites exposing usernames get display names separated from login names, which is the fix that survives future plugin changes rather than the filter that a plugin update can step around.

The two pct=5 DMARC policies get finished. They have been paused mid-rollout for long enough that nobody remembered they were paused, which is the exact failure the report exists to catch.

The domain enforcing DMARC with no SPF gets an SPF record published this week. That is the single most defective row in the table.

The two oversized llms.txt files, at 119 KB and 212 KB, get rewritten by hand. A generator produced them, and generators have no opinion about which twenty links matter.

DNSSEC and CAA get a decision rather than a fix. Both are cheap and both can break things when done carelessly, so the honest outcome is a written decision about whether we want them, not a rushed deployment because they appeared in a table.

What to check first

Ranked table of five checks with time per site: PHP version against php.net dates at 30 seconds, backup restore test at 2 minutes, scheduled jobs running at 1 minute, DMARC policy and pct at 20 seconds, and exposed files at 15 seconds
Check three sits above the mail and exposure checks deliberately. A stopped scheduler

invalidates half the others, because the automation you are relying on has not run either.*

#CheckTimeWhy it is first
1PHP version against php.net dates30sa published deadline, 57.6% of the web affected
2backups restore, not just run2minthe only check that protects every other change
3scheduled jobs are actually running1minif broken, backups and renewals are not running either
4DMARC policy and its pct value20shalf a portfolio typically sits at monitoring, believing it enforces
5exposed files: debug log, config backups15scheapest fix, highest consequence

Nothing on that list takes longer than two minutes per site. The total for a forty-site portfolio is an afternoon, once, and it produces a document you can act on for a year.

The second reading, taken before a month had even passed

This post closed by promising a monthly re-read, with the same numbers measured the same way, including the ones that make us look bad. I re-ran every check in the portfolio table while expanding this post rather than wait for a full month to pass, and the results are worth publishing exactly as promised.

dig +short DS "$d"      # DNSSEC
dig +short CAA "$d"     # CAA
dig +short TXT "_dmarc.$d"

DNSSEC: still 0 of 6. No change. It remains a decision nobody has made, exactly as this post already frames it.

CAA: still 1 of 6, but that one domain moved from incomplete to correct. posimyth.com‘s record now carries matching issue and issuewild entries across five certificate authorities, the specific gap this post’s original table flags, closed. The other five domains still publish none at all.

A discrepancy in this post’s own original table, worth correcting rather than quietly smoothing over: wpagentconnector.com does not have a DMARC policy. This post’s original row states “domains with a DMARC policy: 7 of 7, all.” Re-checking _dmarc.wpagentconnector.com directly returns nothing, an empty TXT lookup, while the domain does publish SPF. Either that domain’s DMARC record existed briefly and was removed sometime between the original check and this one, or the original count was wrong. I cannot determine which from here, and the honest move is to report the current, directly-verified state rather than guess at which explanation is more flattering: as of this reading, it is 6 of 7, not 7 of 7.

The ecosystem-wide PHP figure held almost exactly. Re-querying api.wordpress.org/stats/php/1.0/ directly and summing every branch with no current security support, not only the three largest, gave 38.36% against this post’s original 38.8%, a difference small enough to be ordinary day-to-day movement in a live distribution rather than a real trend in either direction. Unlike a companion post on this blog, whose narrower original figure needed an actual correction, this post’s own number was built the complete way from the start and needed none.

The two pct=5 DMARC policies are unchanged. posimyth.com and theplusaddons.com both still enforce on five percent of failing mail, the exact figure this post lists as “paused mid-rollout, forgotten.” It is still forgotten.

That overall mix, one real fix, one correction to the original post’s own accuracy, and most of the rest sitting unmoved, is precisely what a second, honest reading is supposed to surface. A monthly report that always shows uniform improvement is not measuring honestly. This one plainly does not, and that is exactly the point of running it a second time at all.

Method, so you can disagree with it

Worth stating, because a report without a method is an opinion with numbers attached.

The ecosystem figures come from api.wordpress.org/stats/php/1.0/ and the equivalent WordPress version endpoint, read directly rather than from any intermediary. Support dates come from php.net’s own supported-versions page. Both are linked above and both can be re-read by anybody.

The portfolio figures come from DNS lookups, TLS handshakes and plain HTTP requests against domains we own, run from one machine on 5 to 7 September 2026. Nothing required privileged access, which means you can run the identical checks against your own portfolio and get comparable numbers.

The limits. Eleven domains and sites is a small sample and it is ours, so it is not representative of the ecosystem. The ecosystem figures are self-reported by installs that check in with WordPress.org, which under-counts sites that block those requests. And every measurement is a single point in time.

I would rather publish a small, honest, reproducible sample than a large one nobody can check.

The pattern underneath all of it

Every finding in this report is the same kind of failure. Nothing crashed. No error appeared. Every site returned 200 throughout.

A DMARC policy that enforces on five percent of mail looks identical to one that enforces on all of it. A backup that will not restore sits in the folder next to one that will. A scheduler that stopped in March produces exactly as many alerts as one running perfectly, which is none.

That is the whole argument for measuring on a schedule rather than reacting to alerts. The failures that cost agencies money do not announce themselves, and the only thing that finds them is somebody looking on a day when nothing appears to be wrong.

Protuno’s free audit runs 54 read-only checks from a domain alone, which is where most of the externally-visible numbers above came from. Straight with you as on every post here: the Super Agents that would run this continuously are built and named but not live yet.

I intend to publish this monthly, with the same numbers measured the same way, including the ones that make us look bad. A single reading tells you very little. The second one tells you which direction things are moving.

Comments