WordPress, running like it's 2026

Make WordPress a
platform-native citizen
on Upsun.

A single must-use plugin that teaches WordPress everything about the platform it runs on: environment awareness, router-cache friendliness, read-only-filesystem UX, Site Health checks that understand Upsun, and a wp upsun CLI command. Off-platform it fully no-ops — local dev and CI need no special-casing.

  • v0.3.4
  • MIT
  • PHP 8.1+
  • WordPress 6.0+
  • Composer-first
  • Zero runtime deps
shell
$ composer require artetecha/upsun-wp
[email protected] — feature/checkout

Real subcommands. Real checks. See the CLI →

Why bother

Retire the WordPress workflow you inherited.

Traditional WordPress operations were designed for a single mutable server. Upsun gives you immutable builds, instant production clones, and config from the environment — this plugin closes the gap so WordPress actually behaves that way.

The old way
The Upsun way
Deploys
FTP uploads and cowboy edits on the live box. “Did anyone back up first?”
git push → build → deploy. Immutable, repeatable, one-command rollback.
Updates
One-click plugin updates in wp-admin, straight into production, no record of what changed.
Composer owns every version in a lockfile. The filesystem is read-only — the plugin disables the in-app updaters and removes the Site Health tests that would fail by design.
Staging
A hand-built staging site that drifted from production months ago.
Every branch becomes a full environment — code, database, files — cloned from production in minutes.
Clone safety
Staging quietly emails real customers and talks to live payment APIs.
SafePreviews neuters live outbound integrations at boot: mail intercepted, Stripe forced to test mode, webhooks paused.
Config
Credentials hardcoded per server in wp-config.php, copied around by hand.
Config comes from the environment. The plugin reads PLATFORM_* directly and never defines constants — your wp-config.php stays the single owner.
Caching
A zoo of page-cache plugins fighting each other with drop-ins.
Honest Cache-Control headers drive the platform router’s cache. wp upsun cache-check explains any page’s verdict.
DB changes
“Run this SQL on prod after you deploy” — a sticky note as release engineering.
Ordered, once-per-database deploy migrations via wp upsun migrate. A failure aborts the deploy before traffic switches.

Design principles

Boring rules, applied ruthlessly.

01 Generic first

A feature lands here only if it helps any WordPress site on Upsun. Site-specific behavior belongs in your project, via public filters.

02 No-op everywhere else

Off-platform — local, CI — the plugin loads and does nothing. Malformed platform data degrades silently, never fatals.

03 wp-config wins

The plugin never defines configuration constants. It reads env and constants, and fills runtime-behavior gaps only.

04 Honest about the platform

No feature that pretends an API exists. Limitations are documented instead of papered over — see the router-cache purge story in the roadmap.

05 Every module unit-tested

PHPUnit against WP stubs — no WordPress install required. Thirteen test suites, one per module or subsystem, on a PHP 8.1→8.4 CI matrix.

Flagship module

Preview environments are perfect clones.
That’s the feature — and the hazard.

Upsun previews are byte-for-byte clones of production — including live payment keys, webhook URLs, and CRM credentials. Click around a naive clone and it will happily email real customers and notify real fulfillment systems.

SafePreviews neuters live outbound integrations on every preview, at runtime, from boot — no DB writes, nothing to undo:

  • Mail interceptionwp_mail is intercepted (or redirected to an inbox you choose) on every preview request.
  • Stripe test mode, forced — at option-read time. Cloned live keys stay untouched and unused; missing test keys fail safe.
  • Webhook delivery paused — WooCommerce stops notifying external systems from clones.
  • Your integrations too — the same protection registry is open via upsun_safe_previews_actions, and one-time DB scrubbing hooks into upsun_preview_sanitize.
.upsun/config.yaml
hooks:
  post_deploy: |
    wp upsun sanitize --if-needed

One line catches every clone and every data re-sync — post_deploy is the only hook that runs on both. Fresh clones are detected via an environment stamp; already-sanitized previews no-op; production just refreshes the stamp.

production writes stamp
preview sees foreign stamp → sanitizes once

Opt-in DB sanitizers (all disabled by default): anonymize user emails, anonymize passwords, deactivate listed plugins, scrub listed options — declared once, in versioned config, applied identically to every child environment.

Eleven modules

Everything WordPress needs to know about its platform.

Each module is independently switchable — a UPSUN_DISABLE_* constant in wp-config.php or the upsun_mu_modules filter — and ships with its own unit tests.

environment-indicator

Always know where you are

Color-coded admin-bar badge (branch · environment type) with a Console link, a dashboard widget, and a matching login-screen banner.

page-cache

Router-cache friendliness

Emits Cache-Control: public, s-maxage={ttl} on anonymous, session-free views so the router can cache them; optionally strips cookie spoilers. Commerce bypass patterns come from the integrations layer.

safe-previews

Clones that can’t hurt anyone

Mail interception, payment test mode, webhook pausing — live integrations neutered at boot on every preview. The full story ↑

updates-policy

Read-only, explained

Disables the in-app auto-update machinery (Composer is the update path), replaces the toggles with a note, and removes core Site Health tests that would fail by design.

site-health

Checks that understand Upsun

Object-cache round-trip, cron configuration, writable mounts, preview search visibility — in Site Health, plus an “Upsun” section in the Info tab. Same registry powers wp upsun doctor.

dashboard

A platform home in wp-admin

Top-level “Upsun” page built from real core meta boxes — collapsible, draggable, per-user layout. Environment, services, health, caching, module status. Actions, not settings: configuration stays in code.

cron-heartbeat

Proof cron actually runs

A recurring event stamps a timestamp; staleness (2× schedule warns, 4× fails) and overdue-event counts surface in Site Health, the dashboard, and wp upsun doctor.

preview-protection

Invisible to search engines

X-Robots-Tag: noindex, nofollow and robots meta on non-production — without touching blog_public, because that database is a production clone.

smtp

Mail that just works

Points PHPMailer at the on-platform relay (PLATFORM_SMTP_HOST) unless a mailer plugin already configured SMTP.

writable-paths

Mounts, advised

Integrations declare where plugins write; the check compares that against your declared mounts, and wp upsun mounts prints ready-to-paste YAML for anything missing. Advisory by design — on Upsun the fix is a mount.

mount-usage

See the disk before it’s full

Live disk total/free from the mount filesystem — warns at 80% used, fails at 95%, because a full mount is a rude way to discover a quota. A per-mount size breakdown, computed daily, lands in a “Disk & mounts” dashboard panel.

wp upsun

An ops toolbox inside WP-CLI.

Deploy-hook friendly: meaningful exit codes, machine-readable --format=json|yaml|csv, and every command prints “Not running on Upsun.” and exits 0 off-platform.

shell
$ wp upsun cache-check /shop/ --cookie="tracking=1"
Verdict for /shop/:
  cacheable — s-maxage=600
  Set-Cookie spoilers ..... none
  Bypass patterns ......... no match
  Router fetch ............ HIT (age 214s)
wp upsun info

Project, environment, branch, and routes at a glance.

wp upsun doctor

Runs every health check; exits non-zero on failure — wire it into your deploy hook as a gate.

wp upsun relationships

Services this app can talk to — credentials never printed. --health probes each one live: MySQL ping, Redis INFO, HTTP/cluster status.

wp upsun cache flush

Flushes the object cache — and is honest that the router cache has no purge API.

wp upsun cache-check <url>

Answers the #1 WordPress-on-Upsun support question — “why isn’t my page cached?” — with an itemized verdict.

wp upsun mounts

Declared mounts, plus ready-to-paste YAML for every writable path your plugins need but don’t have.

wp upsun migrate

Applies pending deploy migrations in order, once per database. --dry-run to preview; a failure aborts the deploy.

wp upsun sanitize

Fires preview sanitization (refuses on production). --if-needed is stamp-aware and safe on every environment.

Plays well with others

Integrations, sanitizers, migrations.

Integrations

Everything the plugin knows about a specific third-party plugin lives in one class — and contributes exclusively through the same public filters you can use. Every built-in integration is proof the public API is sufficient. Dormant no-ops when the target plugin is absent.

  • WooCommerce
  • WooCommerce Stripe
  • Wordfence
  • UpdraftPlus
  • WP Rocket

Cart-cookie cache bypasses, checkout cache skips, Stripe test-mode forcing, webhook pausing, writable-path declarations.

Sanitizers

Opt-in, DB-writing, idempotent — all disabled by default. Enable them per-project in the post_deploy hook so the policy is versioned and applied identically to every child environment.

  • anonymize-user-emailsuser-{ID}@upsun-preview.invalid
  • anonymize-user-passwords — one password, or a {ID} template
  • deactivate-plugins — your list, previews only
  • scrub-options — options or dotted sub-keys, e.g. gateway.live_secret_key

Deploy migrations

Every serious WP-on-Upsun project reinvents this — so it’s in the box. Drop ordered files in a directory, each returning a callable:

migrations/
20260701_0001_switch_smtp_plugin.php
20260708_0001_seed_new_terms.php

Run by wp upsun migrate in the deploy hook. Completion markers live in the database, so clones don’t re-run them — and the first failure exits non-zero and aborts the deploy before traffic switches.

Get started

Three steps. No step four.

For any Composer-managed WordPress. The Packagist release (artetecha/upsun-wp) lands with the in-progress extraction to its own repo; until then the package is consumed as a Composer path repository — the steps don’t change.

  1. 1 Require the package

    composer.json
    {
      "require": {
        "artetecha/upsun-wp": "*"
      },
      "extra": {
        "installer-paths": {
          "wordpress/wp-content/mu-plugins/{$name}": ["type:wordpress-muplugin"]
        }
      }
    }
  2. 2 Copy the loader shim

    WordPress doesn’t scan mu-plugin subdirectories, so copy the shim to the mu-plugins root at build time:

    composer.json → scripts
    "post-install-cmd": [
      "cp wordpress/wp-content/mu-plugins/upsun/upsun-loader.php wordpress/wp-content/mu-plugins/upsun-loader.php"
    ]
  3. 3 Wire the post_deploy hook

    One line makes every clone and data re-sync detectable and sanitized. Safe on every environment.

    .upsun/config.yaml
    hooks:
      post_deploy: |
        wp upsun sanitize --if-needed

That’s it. On Upsun, eleven modules light up. On your laptop and in CI, the plugin loads and does nothing — zero special-casing, zero config files to fake.

coming soon Companion repo

Don’t have a Composer-managed WordPress yet?
Start from one.

A starter template repo is in the works: a complete WordPress-on-Upsun project skeleton with everything on this page already wired. Clone it, push it, and you have a running WordPress with previews, caching, health checks, and safe clones from commit one.

  • .upsun/config.yaml — app, services, mounts, and hooks preconfigured
  • Env-driven wp-config.php — credentials, URLs, and salts from the platform
  • Composer-managed core and plugins with installer paths set up
  • This mu-plugin preinstalled, loader shim and post_deploy hook already wired
the whole onboarding
$ git clone <starter-template> my-site # coming soon
$ cd my-site
$ upsun project:create
$ upsun push
 
✔ WordPress deployed — previews, caching, and safe clones included.

Roadmap

Shipped, planned, and honestly blocked.

Shipped

  • 0.1 — core modules: environment indicator, page cache, updates policy, Site Health, preview protection, SMTP
  • 0.2.0 — the “Upsun” wp-admin dashboard
  • 0.2.1 — cron heartbeat + login-screen banner
  • 0.2.2 — SafePreviews + wp upsun sanitize
  • 0.2.4wp upsun cache-check
  • 0.3.0 — integrations architecture
  • 0.3.1 — writable-path advisor + wp upsun mounts
  • 0.3.2 — opt-in DB sanitizers
  • 0.3.3 — deploy migrations, wp upsun migrate
  • 0.3.4 — relationship health (wp upsun relationships --health) + mount-usage module. v0.3 complete.

Planned

  • Extraction to github.com/artetecha/upsun-wp + Packagist release — in progress; the v0.3 trigger has fired
  • v0.4 — premium plugin vendoring toolkit: wp upsun vendor exports installed premium plugins as Composer-ready packages, and reports their pending updates
  • Elasticsearch / OpenSearch → ElasticPress auto-wiring — deferred until a consumer runs a search service
  • The starter template repo above

Blocked — and saying so

  • Router cache purge. The Upsun router exposes no purge API, so this plugin doesn’t pretend one exists. The plan: a documented no-op API surface (Upsun\purge_paths()) with pluggable backends for Fastly/Cloudflare-in-front setups — lighting up natively if the platform ever ships purging.
  • Multisite — delegated to upsun/wp-ms-dbu until there’s demand.