I looked after client WordPress sites for 12 years, and the update failures that hurt most were never the ones that took a site down. A site that is down pages you within a minute. The bad ones are the update that leaves the homepage returning 200 while the checkout button is gone, the contact form quietly stops sending, or a page builder renders an empty column. The uptime monitor stays green, and the client finds it before you do.
This post is the routine I settled on for verifying updates, first on one site and then across many. None of it is new. It's the boring stuff written down in one place, plus a small script you can adapt.
Why uptime checks miss update breakage
A typical uptime check asks one question: did this URL answer with a success code within a few seconds? That's worth knowing, but most update breakage doesn't change the answer.
- A plugin conflict breaks a JavaScript bundle. The HTML still comes back 200, but the slider, the cart or the form doesn't work.
- A theme or builder update changes markup or CSS. The page loads and looks wrong.
- A PHP warning or deprecation notice goes to a log, not the page. Nothing visible changes until a later PHP or plugin version turns it into a fatal error.
- A fatal error hits one template, like single product pages, while the homepage you monitor is fine.
- Page caching keeps serving the old, working HTML, so even a real content check on the homepage passes until the cache expires.
So "is it up?" is the wrong question after an update. The better question is "do the pages that matter still do what they did an hour ago?"
The per-update routine
This is what I do for each site, every time.
1. Back up right before the update, not last night. If something breaks at 2 PM, a 3 AM backup means losing a morning of orders or form entries, or merging them back by hand. A backup from five minutes ago makes rollback an easy call. Save a small manifest with it (WordPress version, active plugins and their versions, table count) so you can tell later whether a restore is complete. This post on restore verification covers manifests well.
2. Update in small batches. Update 14 plugins at once, and when something breaks you have 14 suspects. I do core on its own, then the heavy plugins one at a time (WooCommerce, page builders, form plugins, anything that touches checkout or login), then the rest in small groups.
3. Check key pages before and after. Pick three to six URLs per site that cover the money and the main templates: homepage, contact page, a product page, cart or checkout, a blog post. Before the update, record each page's HTTP status and one string that must be on it ("Add to cart", the form's button text, a footer line). After the update, check again and compare. Before/after screenshots catch layout breakage that text checks miss. If you can do both, do both.
4. Read the PHP error log. Note where the log ends before you update, and read what's new afterwards. New fatal errors, or new warnings that name the plugin you just updated, are the early warning.
5. Decide the rollback trigger before you start. Write it down so you don't argue with yourself at 6 PM. Mine is: if any key page changes status, loses its required text, or a new fatal error shows up in the log, roll back first and debug on staging. Rolling back an update you'll redo tomorrow costs very little. Debugging on a live store costs a lot.
6. Rehearse the restore. A backup you have never restored is a guess. Every so often, restore a real backup to staging, check it against the manifest, and time it. That time is how long a client site stays broken in your worst case.
WP-CLI commands that help
If you have SSH access, WP-CLI covers most of the manifest and the update itself:
# What you're starting from
wp core version
wp plugin list --status=active --field=name
wp plugin list --update=available --fields=name,version,update_version
# Database backup right before the update (database only, not files)
wp db export before-update-$(date +%Y%m%d-%H%M).sql
# Preview which plugins would be updated, without changing anything
wp plugin update --all --dry-run
# Update one plugin at a time
wp plugin update woocommerce
# Put one plugin back to the version you had
wp plugin update woocommerce --version=<previous-version>
# Is debug logging on? If true, the log is wp-content/debug.log
wp config get WP_DEBUG_LOG
A few caveats. --version works for plugins hosted on WordPress.org. Premium plugins usually need their old zip. wp config get errors if the constant isn't defined in wp-config.php. Rolling back a plugin's files doesn't undo database changes it made during the update, which is one more reason the fresh backup matters. All of these also accept the global --ssh= flag or an alias from wp-cli.yml, so you can run them from your own machine.
A minimal before/after page check
This is a deliberately small example. It reads a list of URLs, each followed by a string that must appear on that page, and records the status code and whether the string was found. Run it once before the update and once after. If anything changed, it says so and exits with an error.
#!/usr/bin/env bash
# check-pages.sh: minimal before/after check for a few key URLs.
# Each line of the list file: a URL, a space, then text that must be on that page.
# Usage: ./check-pages.sh pages.txt before (run before updating)
# ./check-pages.sh pages.txt after (run after updating)
set -u
list="$1"
phase="$2"
out="results-$phase.txt"
tmp=$(mktemp)
: > "$out"
while read -r url needle; do
[ -z "$url" ] && continue
code=$(curl -s -L --max-time 20 -o "$tmp" -w '%{http_code}' "$url")
if grep -qF -- "$needle" "$tmp"; then found=yes; else found=no; fi
echo "$url $code $found" >> "$out"
done < "$list"
rm -f "$tmp"
cat "$out"
if [ "$phase" = "after" ]; then
diff results-before.txt results-after.txt > /dev/null \
&& echo "OK: same status codes and text checks as before" \
|| { echo "CHANGED: compare results-before.txt and results-after.txt"; exit 1; }
fi
And a pages.txt:
https://example.com/ Get a quote
https://example.com/contact/ Send message
https://example.com/product/blue-mug/ Add to cart
What it won't do: it doesn't run JavaScript, so a broken cart script still passes. It doesn't see layout. It only sees what a logged-out visitor sees. And if page caching is on, purge it after updating, or the "after" run may be reading the old HTML. It's a floor, not a full test.
Scaling this to 50+ sites
Doing this by hand on one site takes ten minutes. On 60 sites it falls apart unless you add some structure.
Keep an inventory. One row per site: host, PHP version, WordPress version, active plugins, last maintenance date, where backups live, and the key URLs with their required text. The key URL list is the part people skip, and it's the part that makes automated checks possible.
Track staleness with a reason. "Days since last maintenance" with a color is easier to scan than a column of dates. This post on a staleness badge is a nice take on it. I'd add the reason next to the number ("32 days, 4 updates pending, 1 plugin flagged vulnerable") so you know which red site to open first.
Batch by stack and go first on low-risk sites. Group sites that share a theme, builder or WooCommerce. Update a few low-traffic sites in each group, wait a day, then do the rest. Major WordPress releases deserve a longer wait. This post on failure patterns from past major upgrades is a good read on why, including the case where the front end breaks while every admin-side check looks fine.
Only look at what changed. Loop the check over every site and collect one summary. Sixty "OK" lines are noise. You want the three sites that changed.
Report it. A short monthly note per client listing what was updated, what was checked and what was rolled back. It takes minutes once the data exists, and it's the clearest way to show a care plan is doing real work.
Honest limitations
Text and status checks miss visual and script breakage. Screenshots catch more, but they raise false alarms on rotating banners, cookie notices and anything with a date on it, so you'll spend some time tuning. Logged-in flows like checkout or member areas need a test account and a browser tool such as Playwright. And all of this only covers the pages you listed. Picking the right pages matters more than the tooling.
Disclosure
I build WP Foreman, a hosted tool that runs this routine for agencies: a backup before every update, before/after screenshot checks with rollback if a check fails, multi-region uptime checks, SSL and domain expiry alerts, daily vulnerability scans, and branded client reports. The first site is free, then $1.99 per site per month. If you're comparing tools, I keep honest comparisons at wpforeman.com/compare. The routine works just as well with plain scripts.
Top comments (2)
The contact form that quietly stops sending is the one that gets us every time. After every update we now send a real test submission and check the email actually lands. Boring, but it catches more than any status code.
“decide the rollback trigger before you start” is the line i’d pin, arguing with yourself at 6pm is real. updating core alone and then the heavy plugins one at a time makes sense too, 14 suspects is a bad place to be. disclosure: i work at cloudways, where you can take an on-demand backup of an app’s files and database before updating, and that’s the step i’d never skip on any host. for woocommerce sites, do you wait longer than a day after a major release before rolling it out to the rest of the group?