DEV Community

Cover image for A plugin and theme update routine: snapshot, pin, rehearse, then check what broke
AumCreate
AumCreate

Posted on

A plugin and theme update routine: snapshot, pin, rehearse, then check what broke

Most update damage comes from the order of operations. Two plugins updated in the same minute, no snapshot, no record of the version that was running before — that turns a rollback of a few minutes into an evening of guessing which change broke checkout.

The routine below is what I would run on any WordPress site where an update can cost money. It isn't tied to a host or to a particular plugin.

Snapshot the files and the database together

Both, from the same moment. A file backup taken while an order is being written and a database dump taken ten minutes later can disagree about which plugins exist, and restoring that combination produces errors that look like a broken update.

wp db export db-$(date +%F-%H%M).sql
tar -czf uploads-$(date +%F-%H%M).tar.gz wp-content/uploads
Enter fullscreen mode Exit fullscreen mode

A few details that matter more than the commands:

  • wp db export writes into the current directory. Move the dump off the server before you start updating.
  • Files means more than wp-content: wp-config.php, any .htaccess customisations, and drop-ins like an object cache file are part of what you are restoring.
  • A host-level snapshot is convenient, but find the restore button before you need it. Do one restore onto a copy, once, and note how long it took.
  • Sanity-check the dump: grep -c 'CREATE TABLE' db-*.sql should be a plausible number, and the file size shouldn't be a few kilobytes.

If the site takes orders, switch on maintenance mode for the duration of the dump so nothing writes mid-export, then switch it off.

Pin the versions you can't afford to lose

Record what is running before you change it:

wp plugin list --fields=name,status,version --format=csv > plugins-before.csv
wp theme list  --fields=name,status,version --format=csv > themes-before.csv
Enter fullscreen mode Exit fullscreen mode

Keep the previous zip for anything that touches checkout, authentication, or page rendering. WP-CLI can put an exact version back:

wp plugin install <slug> --version=<x.y.z> --force
Enter fullscreen mode Exit fullscreen mode

And stop those plugins from updating themselves while you sleep:

add_filter( 'auto_update_plugin', function ( $update, $item ) {
    $pinned = [ 'your-plugin-slug', 'another-plugin-slug' ];
    return in_array( $item->slug ?? '', $pinned, true ) ? false : $update;
}, 10, 2 );
Enter fullscreen mode Exit fullscreen mode

Themes take the same arguments through auto_update_theme. Core is controlled separately by the WP_AUTO_UPDATE_CORE constant, where minor-only is usually the sensible setting.

Pinning is not the same as never updating. A pinned plugin still gets updated — on your schedule, with the old zip within reach.

Rehearse on a copy carrying production data

A staging site with three sample pages won't reproduce the failure you care about. Clone the production database and the plugin set, then:

wp plugin update --all --dry-run
Enter fullscreen mode Exit fullscreen mode

The dry run lists what would change and touches nothing. Run it for real on the copy afterwards, and click through the paths that earn money: add to cart, checkout, the contact form, login, search.

Watch two logs while you click:

tail -f wp-content/debug.log
tail -f "$(php -r 'echo ini_get("error_log");')"
Enter fullscreen mode Exit fullscreen mode

debug.log only exists when WP_DEBUG and WP_DEBUG_LOG are on. Turn them on for the copy, and keep WP_DEBUG_DISPLAY off so warnings don't land in the rendered page.

Update in batches — core, then plugins, then the theme — with a check between each. When something breaks you want to know which batch caused it, and "all at once" throws that information away.

Check the things that fail quietly

Some breakage is loud: a white screen, a fatal in the log. The rest is subtle, and it surfaces days later as a missing order or a form that never emailed anyone.

  • Forms: submit one and confirm the entry reached the database or the inbox.
  • Checkout: a complete test order, including the confirmation email.
  • Login and password reset.
  • The REST endpoint a block or theme depends on: curl -s -o /dev/null -w '%{http_code}' "$(wp option get home)/wp-json/".
  • Sitemap and feeds: curl -sI "$(wp option get home)/wp-sitemap.xml" | head -n 1.
  • Cron: wp cron event list. Updates occasionally reschedule or drop events, and a missed task shows up later as stale imports or no email.
  • Caches: page cache, object cache, CDN. Purge everything after the last batch — a stale cached page hides both the fix and the failure.
  • Database version: if core asks for an upgrade, snapshot again immediately before wp core update-db.
  • Image sizes: if a plugin changed its registered sizes, thumbnails may need regenerating.

Diffing the CSVs answers "what actually changed" faster than scrolling the admin:

wp plugin list --fields=name,status,version --format=csv > plugins-after.csv
diff plugins-before.csv plugins-after.csv
Enter fullscreen mode Exit fullscreen mode

Roll back files and database together

Plugin updates run database migrations. Restoring only the plugin folder can leave the database ahead of the code, which shows up as missing tables or a fatal on the next request.

wp db import db-YYYY-MM-DD-HHMM.sql
wp plugin install <slug> --version=<x.y.z> --force
Enter fullscreen mode Exit fullscreen mode

That restore discards everything written after the dump was taken — which is exactly why the snapshot goes immediately before the update, inside the maintenance window, and not the night before.

If the site genuinely can't go down, the fallback is to restore files only and then check whether the plugin's migration is safe to re-run. That's a per-plugin judgement call, and a decent argument for preferring plugins that don't migrate on update in the first place.

A routine short enough to run every time

A procedure you skip under time pressure is worse than no procedure. The version that fits in ten minutes:

  • Export the database and archive uploads; move both off the server.
  • Write plugin and theme versions to CSV.
  • Update on the copy first, then click through forms, checkout, login and search.
  • Update production in the same order, one batch at a time, in a low-traffic window.
  • Purge caches; check the error log, cron, sitemap and REST endpoint.
  • Keep the previous zips and the dump until the next round of updates.

Nothing here is clever. It's the ordering that saves the evening, not the tooling.

Top comments (0)