Home → Services → WordPress update automation
For a Dutch web agency we build and maintain their clients' WordPress sites. Core, plugin and theme updates were done by hand, site by site, and that limited how often they could happen. The result is our own maintenance plugin and a script started from the server's scheduler: it updates, checks that the site still works afterwards, and sends a report. Below is what we learned along the way — nearly all of it only showed up in practice.
WP-Cron fires when someone visits the site. On a site nobody visits for a week, scheduled tasks simply stay silent. So the updates are started by an entry in the host's scheduler: a PHP script that loads WordPress itself.
That moved the problem somewhere else. Outside an HTTP request the $_SERVER array is nearly empty, so plugins with typed methods return null from it and die on a TypeError — while WordPress is still loading, which is before anything was able to write a report about that error. The script therefore fills in safe values before wp-load.
It never touches wp-config.php. It recognises the settings in there that are worth knowing about and writes about them, but the decision stays with a person: that file holds custom constants, and a typo takes the site down.
It does not roll an update back on its own either — the report lists the versions before and after, so there is something to go back to. And a fix for one site stays on that site's server, with a backup and a note; it does not go into the code that ships to all of them.
Some things an automation should not update. The hold-back list was written after a Bricks update took one site down for half a day. The cause: 2.4 fires template hooks through do_action_ref_array(), and WordPress only pads empty arguments in do_action() — so child-theme callbacks with a required parameter started dying on ArgumentCountError. On a manual run on that same site the automation updated Bricks anyway, despite its entry on the list: the list was only consulted for plugins, and Bricks installs as a theme.
The second finding that same week: saving one settings tab reset the list to empty. The save handler only processes fields belonging to its own tab, and the assignment had been put in another tab's block — so every save sent the value to ''. Fixing both was the condition for moving from a weekly run to a daily one.
After an update a shop has to be checked, because a broken checkout does not report itself. The first version placed a test order and cleaned up after itself — it set the status to “cancelled” and moved the order to the trash. The “cancelled” status triggers WooCommerce's own e-mail, and the trash hides the order: shop owners were getting mail about a cancelled order they could not find. Filters do not help, because that mail goes out from a queue, in a later request. So the cleanup moved to a permanent delete — which deleted nothing at all on shops using the new order tables, where orders no longer live among the posts.
Today the automation creates no order. It walks the shop the way a customer does — home page, product page, cart through the Store API, checkout page — and stops in front of the payment button. On shops with integrations a new order wakes up the bookkeeping and the mailing system within seconds, and some of them assign an invoice number the moment an order is created.
Core and plugin files are compared against the checksums from wordpress.org. They reported too much: the host writes php_errorlog into whichever directory the failing script ran from — deep inside core as well; one site has an old .htaccess in core that blocks access and does nothing else; on another, a migration years ago changed the line endings in dozens of files. Each of those got its own rule, rather than an exemption covering a whole plugin. Files the host's own scanner has already moved into quarantine are reported separately.
The other direction was worse. When the tool returned nothing, the empty list of differences fell through into the “no changes” branch — being unable to verify was reported as a clean result. “Could not verify” is now a state of its own.
The cron script sits in the WordPress directory and the plugin copies it there itself. It did that on the plugin-update event and when a signed-in administrator opened the dashboard — and a bulk install through MainWP or a copy over SFTP fires neither of those. The dashboard showed the new version, the nightly run executed code from many versions back, and nothing anywhere said so. Today the version is compared on every run and on an ordinary visit to the site, with nobody signed in.
Tell us how many sites there are and whether any of them are shops. We'll say what can safely run on a schedule and what still has to be done by hand.
Describe your sites →Or by e-mail: info@asymetria.com.pl