Table of Contents
Last update on
On August 20th, WordPress 7.1 went out, and within hours, some sites running WP Rocket started going down with a fatal error. Some of you lost your front end. Some lost your dashboard too, with no way in to fix it yourselves.
This was our fault, because we didn’t take action on time. We want to walk you through exactly what happened, why it took us as long as it did, and what we’re changing so it doesn’t happen again.
What actually broke
WP Rocket includes a small module that talks to Cloudflare, and it’s active whether or not you use Cloudflare at all. As part of its cleanup routine, that module calls a PHP function, substr(), on the ID WordPress generates for callbacks on two specific hooks. Before WordPress 7.1, that ID was always a string. PHP 8 enforces strict typing, so passing anything other than a string into substr() throws a fatal error instead of quietly continuing.
WordPress 7.1 changed how those callback IDs are built, and under specific conditions, the ID can now come back as an integer instead of a string. This was confirmed as a breaking change on WordPress’s own side by Weston Ruter, a Core maintainer, on August 21st. A Core contributor has since opened a ticket with a proposed fix, which is expected to land in WordPress 7.1.1.
Those specific conditions only happen when another active plugin registers a callback in a particular way. We went through the 200 most used plugins on WordPress.org, user reports, and public GitHub activity to identify exactly which ones. Here are the examples we have found triggering it, and we want to be clear: none of these plugins are doing anything wrong. They simply happen to match the conditions that exposed our bug.
- Elementor Pro, as soon as the user has a valid license and the plugin is activated
- Elementor Free, if the experimental e_atomic_elements setting is active
- Redirection for Contact Form 7, as soon as the plugin is activated
As a summary, the issue appears under a specific combination: WordPress 7.1, PHP 8.x, WP Rocket, and at least one other plugin registering callbacks to specific hooks in a specific way. While specific, this combination turned out to be very common, which explains the impact of this issue. Based on the plugins confirmed to trigger the issue and update patterns across our user base, we estimate that around 27% of WP Rocket websites were at risk, and that roughly 10% were actually impacted.
The timeline
July 6th. This is when the issue was first reported to us: a WP Rocket user opened a GitHub issue, testing against the WordPress 7.1 alpha. It correctly identified the bug and suggested the fix we’d eventually ship. Our QA team looked into it that same day, ran our automated tests against the WordPress nightly build, and everything passed. They tried to reproduce it and couldn’t. There was no further update on the report after that. Over the following weeks, a couple of customers also ran into the same fatal error and opened support tickets about it. Each case was resolved by reverting to the stable WordPress build, without the connection back to the open GitHub issue being made at the time.
July 15th. WordPress 7.1’s first beta went out. The bug was already in it. Our automated and manual tests against that beta all passed. We marked WP Rocket as tested and compatible up to 7.1.
August 19th, 23:39 CEST. WordPress 7.1’s release candidate went out at a release party, and support tickets about a fatal error started coming in that same night. This was outside our plugin team’s normal working hours, but a developer from a different team saw the escalation, dug in, and confirmed the root cause.
August 20th, 00:35 CEST. We had a manual fix. At this point it hadn’t gone through our normal QA validation yet, so rather than publish an unverified workaround more broadly, we sent it directly to everyone who already had a support ticket open, along with a note that an official update was on its way.
August 20th, 02:18 CEST. WordPress 7.1 officially released. Every site with auto-updates enabled started updating, and could hit the bug from that point forward.
August 20th, 04:08 CEST. Our support team published a documentation article covering the issue and the available workaround.
August 20th, 07:30 CEST. The WP Rocket plugin team started their day and picked this up. Public communication went out within 15 minutes: in-product notices, social media, a status page update, and replies on every related GitHub issue, pointing everyone to the support article and letting them know a fix was coming.
August 20th, 10:11 CEST. WP Rocket 3.23.2.2 shipped, validated through our automated tests and targeted manual testing. We watched every channel we had for confirmation it was working, and it was.
August 21st. We emailed every active WP Rocket customer to explain what happened and recommend updating WP Rocket before updating to WordPress 7.1.
Why our testing didn’t catch it
We know this is the question that matters most, and it’s a fair one given how widely this hit. Here’s how our testing process works, and where it fell short.
It starts at the pull request stage: every change is reviewed by another developer, including a manual test of the change and confirmation that automated tests pass across multiple WordPress and PHP versions. Those tests are publicly available on our GitHub repository. From there, a QA engineer validates the change against the latest WordPress nightly build to catch regressions before anything gets merged.
Once merged, the development version runs continuously against roughly a hundred automated end-to-end tests, on both NGINX and Apache, against the latest WordPress version including betas and release candidates. That suite is also public. It checks compatibility against a specific set of third parties: Query Monitor, Imagify, Cloudflare, WPML, Divi, Avada, Astra, Flatsome, Storefront, Hello Elementor, Neve, Kadence, GeneratePress, Genesis Sample, and OceanWP.
That list wasn’t built by asking which plugins our users run most. It grew opportunistically, over years: as we improved compatibility with one plugin or theme, we added automated tests to make sure we didn’t regress on it later. That’s a reasonable way to grow a test suite, but it left real gaps, and Elementor Pro not being on that list is one of them.
Before a new version ships, we also release it internally as a release candidate for the whole team to run additional exploratory testing, and then roll it out to customers progressively over a few days rather than all at once, specifically so we can catch anything a broader release would surface and ship a hotfix before it reaches everyone. None of that caught this, because the issue only appeared with third parties that weren’t part of our automated checks, and no manual test plan in recent releases happened to include them either.
What we’re changing
Someone now owns GitHub triage. The July 6th report was reviewed but never assigned to an owner, so it fell out of view once the initial check didn’t reproduce the issue. We’ve put clear ownership in place: every report is triaged, categorized, escalated to product when relevant, and kept current as new comments come in.
We’re rebuilding what we test against, on purpose. Our compatibility test suite reflects years of incremental additions, not a deliberate map of what our users actually run. We’re now building that list properly, starting with the top plugins and themes most commonly used alongside WP Rocket, and we’ll keep growing it based on 3rd party popularity and the compatibility work we carry out over time. WordPress is too open an ecosystem for us to promise full coverage of every possible configuration, but we can promise this list will be built with intent instead of history.
We’re also shipping a smaller, immediate fix. The Cloudflare module was running on every site regardless of whether Cloudflare was even in use. We’re gating it behind a check for whether the Cloudflare plugin is actually active, so this specific code path won’t run at all on sites that don’t need it.
A few more things are on our list once the above is done: getting faster between internal escalation and public communication, cutting down unused code paths that shouldn’t be loading in the first place, and shortening the time it takes us to validate and release a hotfix once we know what’s wrong.
Where that leaves us
This cost real time and real trust, and both matter to us. We’re truly sorry for the outage, for the stress, and for the fact that it didn’t have to happen this way.
To everyone who reported the issue, worked around it, or helped others do the same: thank you. Your work made this shorter and less painful for other people than it would otherwise have been.
We know saying sorry doesn’t rebuild trust, time and actions do. The changes above are our attempt to get better over the long run, and to earn that trust back through what we actually do next.