CI & Workflow Integration

Give WordPress Updates a Release Process

Every other piece of software you ship has a release process. Branch, review, CI, staging, deploy, rollback. Then there is the WordPress site the marketing team owns, where "deploy" is a person clicking Update All on production at 4pm on a Friday, and rollback is a support ticket.

The gap is not a WordPress criticism. Its update UI is genuinely one of the reasons the platform stays patched. The problem is that the same UI applies changes to a live environment with no test run, no gate and no defined way back — which is fine for a hobby blog and indefensible for anything a business depends on.

Here is what a minimum viable release process looks like for a WordPress site, using tooling you already know.

The four properties you are actually buying

Whatever shape your pipeline takes, it exists to give you four things:

  1. A known state. You can say exactly which versions of core, plugins, themes and PHP are running, and prove the files match what the vendors published.
  2. A place to try it first. An environment that resembles production closely enough that a failure there is informative.
  3. A gate. Something automatic that fails when the site is broken, so the decision to proceed is not a human squinting at a homepage.
  4. A way back. A rollback that takes minutes and does not depend on the person who did the update being awake.

Most WordPress sites have none of the four. Getting to three of them is a day's work.

1. Know the state before you change it

wp-cli is the interface that makes any of this scriptable. Before touching anything, capture what is there:

wp core check-update
wp plugin list --update=available --fields=name,version,update_version
wp theme list --update=available --fields=name,version,update_version

# Does what's on disk match what the vendor published?
wp core verify-checksums
wp plugin verify-checksums --all

The checksum commands are the WordPress-specific integrity check and are worth running on a schedule regardless of updates. Core and any plugin hosted on wordpress.org publishes file hashes; a mismatch means a file has been modified since it was installed. That is either an undocumented "quick fix" someone made directly on the server — which the next update will silently overwrite — or something worse. Either way you want to know before you update, not after.

Record the output. It is your before-state, and it is what a rollback targets.

2. Stage the change, not the hope

A staging environment is the highest-leverage item on this list and the one most often skipped because it feels like infrastructure work. The pragmatic version does not need to be elegant:

# On staging, from a production export
wp db import production.sql
wp search-replace 'https://example.com' 'https://staging.example.com' --skip-columns=guid --dry-run
wp search-replace 'https://example.com' 'https://staging.example.com' --skip-columns=guid

# Keep staging from mailing real people and from being indexed
wp option update blog_public 0

Two rules that matter more than the mechanics. Staging must be non-public and non-mailing — a staging site that sends order confirmations to real customers is worse than no staging at all. And staging must be refreshed from production, because a staging site that has drifted for eight months tests a site that no longer exists.

Then apply the update there first:

wp plugin update --all --dry-run      # see the plan
wp plugin update --all
wp core update --minor

--minor on core is a useful default for scripted runs: it takes security and maintenance releases within the current major line, leaving major version jumps as a deliberate, separately scheduled event.

3. Gate it on something automatic

The gate does not have to be sophisticated. It has to be automatic, because the failure mode of manual verification is a tired person deciding the homepage looked fine.

A shell smoke test covers most of the value:

#!/usr/bin/env bash
set -euo pipefail

BASE="${1:?usage: smoke.sh https://staging.example.com}"
PATHS=(/ /contact/ /about/ /wp-login.php)

for path in "${PATHS[@]}"; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' "${BASE}${path}")
  if [[ "$code" != "200" ]]; then
    echo "FAIL ${path} -> ${code}"
    exit 1
  fi
  echo "ok   ${path} -> ${code}"
done

# PHP fatals land in the log even when a page still renders something
if wp --path=/var/www/staging eval 'echo "boot-ok";' | grep -q boot-ok; then
  echo "ok   wp bootstraps"
else
  echo "FAIL wp does not bootstrap"
  exit 1
fi

Add the paths that carry money or leads — the contact form's page, the checkout, the pricing page. Wire it into whatever runs your other checks; if you already gate merges on lint and tests, this is another job in the same workflow, and the linting in CI guide covers the pipeline-shaping side of that.

4. Static analysis belongs on the custom code, not the vendor code

There is no value in running a linter over WordPress core or a third-party plugin — you are not going to fix it, and the noise will bury anything real. There is a great deal of value in running analysis over your theme and your custom plugins, especially before a platform or PHP version bump.

Three checks that repay the setup:

# Syntax only — fast, catches the catastrophic
find wp-content/themes/mytheme -name '*.php' -print0 | xargs -0 -n1 php -l

# Coding standards, using the WordPress ruleset
phpcs --standard=WordPress wp-content/themes/mytheme

# Will this code survive the PHP version the server is moving to?
phpcs --standard=PHPCompatibilityWP --runtime-set testVersion 8.2- wp-content/themes/mytheme

That third one is the underrated one. PHP version upgrades are where custom WordPress code breaks, and a compatibility ruleset tells you in advance which files will be a problem — before your host moves the site and you find out from a white screen. If you want deeper analysis of the custom code, a static analyser with WordPress stubs (so it understands core's function signatures) will find the null and undefined-variable classes of bug that a standards check does not; the static analysis guide covers what that tier adds and what it costs to adopt.

5. Make the way back cheap

Rollback for WordPress is two artefacts and one command family:

# Before: capture both halves
wp db export "pre-update-$(date +%F).sql"
tar czf "pre-update-$(date +%F)-content.tar.gz" wp-content

# After, if a specific plugin is the culprit
wp plugin update woocommerce --version=8.9.1

wp plugin update --version= is the reason to keep the before-state list from step 1: with the previous version numbers written down, reverting a single plugin is one command rather than an archaeology exercise. Note the asymmetry that catches people out — reverting files is easy, reverting a database migration a plugin performed on activation is often not. That asymmetry is exactly why the database export is taken before, and why staging matters most for plugins that touch schema.

When it should not be your pipeline

Everything above is a day to build and then a recurring obligation: someone has to refresh staging, watch the smoke test, and hold the update window every week. For an agency with several sites, that is worth owning. For a single business site whose developer has other priorities, the honest answer is often to buy the cadence instead.

If that is the situation, WPCare is a WordPress maintenance team whose published service model maps directly onto this article: scheduled weekly updates rather than ad-hoc ones, private staging environments on its higher tiers, and continuous off-server snapshots as the rollback artefact. The reason to mention it here is not that outsourcing is better — it is that the model matches the shape of the problem. An update process only protects you if it happens on a schedule, and a schedule is precisely the thing that decays when updates are somebody's fifth priority.

If you do outsource, the questions worth asking are the four properties at the top: how do you record the before-state, where do you test, what automatically fails, and how fast is the rollback.

FAQ

Should I just enable automatic updates? For core minor releases, usually yes — they are security and maintenance releases within the same major line, and the risk of applying them is lower than the risk of not. For plugins, automatic updates trade a verification step for a timeliness gain. Enabling them on a site with no smoke test means breakage is discovered by a visitor.

Is a staging site needed if updates are small? The size of the diff is not the useful signal — a one-line change in a plugin that touches the checkout is riskier than a major version bump of a plugin nobody uses. Staging is what lets you stop guessing which is which.

Can I run WP-CLI in CI without SSH access to production? You can run the analysis and the smoke test from CI against a staging URL without any server access at all. The update commands themselves need a shell on the host running WordPress; where that is not available, a deployment-hook or agent-based flow from the hosting platform is the usual substitute.

Does verify-checksums cover premium plugins? No. It verifies core and plugins distributed through the wordpress.org directory, because that is where published hashes exist. Commercial plugins installed from a vendor ZIP are outside its scope, so track their versions and source separately.

The bottom line

WordPress does not stop you from having a release process; it just does not give you one. Capture the state, test on a refreshed and non-mailing staging site, gate on an automatic check, keep both rollback artefacts, and point your static analysis at the code you actually own. If nobody in-house will hold that cadence every week, buy it — WPCare sells that exact shape of service — but do not run WordPress updates with no environment, no gate and no way back, on a site that matters.

Comments are disabled for this article.