Technology

WordPress and Laravel: From Staging to Production

Imagine a small WordPress plugin update, a theme change and a Laravel migration released together. A stalled homepage or broken checkout can follow even when the patch looks simple. The useful question is whether the tested build, shared data and running processes can move together through a controlled deployment and recovery plan.

This guide covers a development–staging–production workflow for WordPress and Laravel: source and environment separation, media, release directories, migrations, cache, workers and health checks. Zero downtime is a target to validate. Neither a small change nor an atomic code switch guarantees correct requests or reversible data changes.

Why Separate Development, Staging and Production?

A plugin update can disrupt a template, background task or checkout integration. Staging provides a chance to reproduce those failures before users encounter them. Test database upgrade routines and recovery as well as the visible page; a file rollback may not undo the plugin’s data changes.

Development supports rapid changes and automated tests. Staging exercises a release candidate under production-like PHP, database, cache and proxy behavior. Production holds live business state. Record differences in data scale, traffic and infrastructure; matching software versions does not make staging a perfect predictor of production.

Require meaningful automated and functional checks before promotion. Choose test depth from affected behavior, including authentication, payments and background work. Passing staging reduces uncertainty but does not justify treating peak-hour deployment or rollback as risk-free.

The Backbone: Git, Environments, and What Lives Where

Branching that serves the rollout

Feature and staging branches are one workable model, not a requirement. What matters is promoting the tested commit and identified build artifact. If a merge or rebuild changes the output, validate that new output. Record the commit, dependency lockfile, artifact digest and environment configuration version for each release.

Environment separation that feels natural

Each environment needs separate database, queue, cache, storage and external-service credentials. Select WordPress configuration through trusted deployment settings, never an unchecked request hostname. Protect .env/wp-config and generated config caches, keep production debug output disabled and route staging mail, payments and webhooks to test destinations. Do not copy a production credential set merely to make staging resemble production.

Shared vs. versioned files

Version application code and keep persistent uploads/storage outside releases where appropriate. Release-specific caches such as bootstrap/cache belong with the release. If Laravel storage is wholly shared, its default compiled-view directory is shared too: view:cache clears that directory before rebuilding. Configure view.compiled to a release-local path such as base_path(‘bootstrap/cache/views’) and create it before cache generation. Shared state makes files available across releases; it does not make their formats rollback-compatible automatically.

Media uploads and the staging trap

Copying entire upload directories between environments can overwrite or delete live media. Give staging a separate write/delete area. If it needs production public media, use a reviewed one-way copy or read-only access; private files need separate or appropriately anonymized fixtures. Object storage is not automatically a CDN or backup. The WordPress media offload guide covers storage, CDN and signed-URL options; those controls do not replace environment permission boundaries.

Release Preparation and Atomic Code Switching

The “release” directory dance

Prepare a candidate in a separate release directory rather than overwriting live files. A temporary symlink renamed over current in the same parent directory can avoid a missing-path window; ln -nfs does not provide the same guarantee. This atomic filesystem step does not make the database, caches or a group of HTTP requests a transaction. Retain old code and assets while processes and clients still use them.

The pattern can support both stacks when shared state and runtime paths are designed for it. Serialize deployment, rollback and cleanup through one lock; multi-host deployment needs shared coordination. The VPS CI/CD guide using rsync, symlinks and systemd covers implementation details. Release directories alone are not blue-green deployment, which switches traffic between two running environments.

Build in CI, deploy the artifact

Build frontend assets and test the identified package in CI. Dependencies built there must match the target PHP platform and extensions; otherwise install the lockfile’s versions in the candidate. Application Composer scripts may bootstrap Laravel, so attach the target environment and shared paths before running them. Exclude CI secrets, local .env files and staging config caches. Generate environment-dependent config/route/view caches in the prepared candidate; they are not the same as post-activation page-cache warming.

GitHub Actions can build, test and pass an artifact to deployment. Verify artifact identity, required job success and SSH host keys rather than copying an arbitrary workspace. A digest mismatch must fail your deployment gate; a tool warning alone is not an enforced check. Keep deploy credentials narrowly scoped and record each step’s result.

Reloads, not restarts

Use the supported Nginx/FPM lifecycle for actual configuration changes. Nginx may not need reload when only current changes. A symlink flip does not guarantee fresh PHP bytecode: OPcache, realpath caching and preloading depend on the runtime. Resolve each request’s scripts and includes consistently within a release, verify new-worker identity and keep old directories until active requests and jobs finish.

Health checks and the “is it really ready?” moment

Check the candidate through a private preview listener or vhost explicitly bound to its real path, intended PHP runtime and target environment. Testing the public current URL checks the old release. Define readiness for the application and required dependencies; Laravel’s default /up route does not automatically prove database or queue health. Use side-effect-free requests, and remember that booting WordPress/plugins can itself run hooks. A failed pre-switch gate prevents activation, but any earlier migration may already have changed shared state.

Database Changes Without Drama

The cardinal rule: backward compatibility during rollout

Choose migration and code order so every running version remains compatible at every step. Expand-and-contract commonly adds compatible structures, deploys transitional readers/writers, backfills data, then removes old structures after old processes and the rollback window are gone. Sometimes code must precede schema, sometimes schema precedes code; there is no universal order that makes arbitrary migrations safe.

Laravel migrations, the calm way

Check DDL behavior for the exact database engine and version. Nullable columns, defaults and online tools do not inherently guarantee nonblocking changes. Backfill in bounded, restartable batches and verify the data before enforcing constraints. Before NOT NULL, fill existing NULLs and retire writers that can create more. Laravel migrate –force only suppresses confirmation. Pausing consumers does not stop web requests or all external writers, so coordinate the actual mutation sources when compatibility cannot be maintained.

The Laravel VPS deployment guide explains release directories, Horizon and process transitions. Use it with a migration plan appropriate to the application’s data and recovery objectives.

WordPress updates, serialized data, and WP‑CLI

WordPress plugins can ship database upgrades and background migrations. Test the relevant settings and business flows, not just activation. For actual URL changes, WP-CLI search-replace handles serialized data; inspect a dry run scoped to the intended installation, tables and columns. Preserve GUIDs in routine URL moves. A production-to-staging URL rewrite is not a production deployment step. Live replacements need production-specific values and a recoverable backup; avoid exposing row contents or secrets in verbose logs.

A phased plugin upgrade is possible only when the plugin supports the intermediate representations and upgrade path. Verify its documentation and actual background jobs. If it cannot keep old/new code compatible, plan an appropriate maintenance and recovery procedure. Restoring an old database snapshot may discard new orders and other writes, so define reconciliation before relying on it.

WordPress: Themes, Plugins, Caching, and Real‑World Gotchas

Version your theme like an app

Treat custom themes and plugins as versioned application code. MU plugins are useful for reviewed always-on integration code, but they do not run normal activation hooks or receive normal update notifications; arbitrary plugins cannot simply be moved there. Composer repositories can lock supported plugin versions, while licenses, runtime configuration and database state still vary. Reconcile live dashboard/automatic updates with the release manifest to avoid unmanaged code drift.

Cache warming the friendly way

Warm only inspected public, non-personalized pages after invalidating affected source and CDN entries. Anonymous visitors can still hold a cart or personalized price. Exclude actual cart/checkout/account paths, session and authorization contexts, action requests and personal Store API responses; check first Set-Cookie responses too. The WordPress and WooCommerce full-page cache guide details those boundaries. Test two customer sessions rather than treating a high hit ratio as success.

Media sanity and CDN behavior

Version asset URLs and make HTML reference the matching version. Keep old assets available while cached HTML or open clients use them. Purging only a CSS/JS URL may leave stale HTML, while a CDN purge does not clear browser caches. Invalidate source HTML before edge dependencies and verify the actual new body. A global template change may require broader invalidation than one asset path.

WP‑Cron vs real cron

If using a system scheduler, configure and verify its trigger before disabling page-driven WP-Cron with DISABLE_WP_CRON. Monitor due-event completion, overlap and failures; a timer schedule is not proof that tasks finish on time. Reproduce the chosen scheduling model in staging with test endpoints and isolated queues so cron cannot send customer messages or mutate production data. Avoid accidentally running duplicate scheduler mechanisms.

Laravel: Queues, Horizon, Octane, and Smooth Switchovers

Queues behave best when you guide them

Horizon manages Redis-backed Laravel queues; ordinary queue workers are separate CLI processes. Prepare compatible code/schema and identify every consumer. Pausing consumption creates a backlog and can delay user-facing work. Let valid active jobs finish, respect timeout/retry settings and verify idempotency for repeated external effects. Starting new workers does not make older queued payloads compatible automatically.

Horizon, Octane, and friends

Prepare the target environment, shared links and release-local config/route/view caches before activation. With a controlled Horizon transition, stop it through systemd/Supervisor, wait for jobs and verify the stop result; this prevents auto-restart from opening the old current before the switch. Activate the prepared release, then start the manager’s command and working directory against the new path. For Octane/RoadRunner, worker reload is sufficient only if it actually resolves that release; a server tied to an old working directory may retain old code. Verify release identity or replace the server process using tested draining/traffic routing. Reloading a single serving instance may affect availability, so do not promise an invisible transition.

Config discipline pays dividends

Keep .env files and other secrets outside source control and scoped to the environment. Build the candidate’s config cache only after the target values are available. Normal .env loading stops once config is cached; env calls belong in configuration files. Preserve required encryption-key compatibility and do not run key generation on every deploy. Config caches can contain secrets too and need appropriate permissions.

Validate Each Stage of the Rollout

On your machine: prototype and break things guilt‑free

Develop with matching PHP/extension and database behavior, using realistic synthetic fixtures and Laravel factories/seeders where helpful. Exercise failure modes such as dependency latency, retries and missing permissions. Matching versions is necessary evidence for some issues, but cannot substitute for realistic data size and production-like load.

On staging: rehearse like the show is sold out

On staging, enable the intended cache and scheduling layers with isolated data and credentials. Test login, carts, checkout, password resets and webhooks using test accounts and sandbox/sink endpoints. Rehearse failed activation and rollback, checking schema/data/cache/payload compatibility and restarting long-lived processes from the rollback path. Verify HTTP and job outcomes after the switch; flipping current back alone is insufficient.

On production: flip fast, watch calmly

In production, acquire the deployment lock and prepare the identified artifact, target environment, shared paths and permissions. Generate candidate-local caches, apply only reviewed compatible migrations and run the candidate health gate. Coordinate consumer drain, switch code or traffic, and start processes against the new release. Then invalidate public page-cache dependencies, warm eligible pages and verify live responses plus job results. Observe metrics for a period suited to the business cycle and risk rather than an arbitrary ten minutes.

Feature Flags, Dark Launches, and “Try It Without Telling Everyone”

Feature flags can expose a change to selected users after deployment. Define the disabled behavior and ensure it does not access unavailable schema. Keep flag-dependent cached responses separate or bypass them. A database or cache-backed toggle has propagation behavior to test, and a config-cached flag may require configuration regeneration/process transition. Turning a flag off does not undo data already written.

A dark launch can deploy backend code before exposing the interface. Controlled traffic can exercise it, but hidden code is not automatically validated or free of side effects. Keep authentication, authorization and monitoring in place for internal test access.

Security, Secrets, and Those Tiny Things That Matter

Use separate staging keys, accounts, callback URLs and storage/queue namespaces, plus payment sandbox credentials and mail/SMS sinks. Sanitize necessary production data before it reaches staging users; changing email addresses alone does not anonymize all personal data. Prefer synthetic fixtures where possible. Protect staging with authentication or network access controls; noindex/robots instructions alone do not restrict access. Check cron and outgoing webhooks as well as interactive requests.

Transport security is separate from release switching. Verify HTTPS certificates, renewal, trusted-proxy settings and security headers for the visitor-to-edge and edge-to-origin connections. Zero-downtime deployment does not establish that either connection is correctly secured.

Monitoring and State-Aware Recovery

Know when something’s off—without panic

Correlate release identifiers with HTTP errors/latency, resource saturation, slow queries, job age and completion rate. The VPS monitoring guide using Prometheus, Grafana and Uptime Kuma provides a starting point. Define actionable thresholds and an owner for investigation or recovery; an attractive dashboard alone is not a deployment gate.

Make rollback a first‑class citizen

Before rolling back, verify that preserved code works with the current schema, data, environment, cache formats and queued jobs. Under the same lock, coordinate process shutdown, atomically select that release and restart services/workers from its actual path. Validate health and business flows afterwards. The switch does not undo database writes or external effects; a forward correction or reconciliation may be safer than a destructive schema rollback.

Complete WordPress and Laravel Release Sequences

WordPress Release Sequence

Build and test the identified theme/plugin artifact, then prepare a new release with target wp-config and shared uploads under the deployment lock. Validate code, permissions, database compatibility and any plugin upgrade plan. Only if URLs actually change, use a backed-up, scoped, serialization-aware dry run before applying replacements. Check the candidate, switch consistently, coordinate PHP/scheduler processes, invalidate affected public HTML and warm eligible pages. Verify user flows and background jobs before retiring old releases; never copy the staging database wholesale over live business data.

Laravel Release Sequence

Prepare the candidate’s environment/storage before application scripts, install the locked dependencies and build assets, then generate release-local config/route/view caches. Run reviewed compatible migrations and a candidate health gate. Stop and drain consumers through their manager, switch code/traffic and start long-lived processes against the new release. Verify live HTTP, release identity and job results; page-cache warming is a separate post-switch step. Recover only after checking schema, data, cache and payload compatibility, and retain old code/assets until safe to retire.

Both stacks benefit from an identified build, isolated environments, explicit shared-state rules, locked activation and tested recovery. The detailed acceptance checks differ by application and plugin behavior; no single shell command proves the whole release is safe.

A Quick Word on Traffic Spikes and Caching

A cold cache, purge or deployment overlap can expose origin capacity limits. Cache only public responses that meet the policy, and keep dynamic/session traffic and bounded queue demand in the load model. Warm and cold-cache tests should measure errors, latency and completed work. A quiet graph alone does not prove users or background jobs are unaffected.

Real‑World Extras You Might Appreciate

For media and environment boundaries, use the WordPress S3-compatible media guide. The Laravel deployment guide covers release and worker behavior, while the rsync, symlink and systemd CI/CD guide details the infrastructure steps. Apply each with the actual application’s state and recovery requirements.

The WooCommerce full-page cache guide develops privacy and session exclusions. The monitoring and alerting guide supports measurement. Neither replaces testing your own release and business flows.

Keep the Workflow Reproducible

Build separately, validate the candidate, preserve shared data, coordinate processes and verify the active release. Plan migrations and rollback around the states that really exist. Atomic switching is one useful primitive within that workflow; the evidence for availability and correctness comes from measured requests and completed jobs.

Keep the artifact identity, environment prerequisites, migration state, health results and recovery decision in the release record. Rehearse failures as well as success and revisit the procedure when PHP, Laravel, plugins, cache or worker infrastructure changes.

A repeatable workflow makes the next deployment easier to assess. Its documentation should allow another operator to determine which version is active, what shared state changed and which recovery options remain compatible.

Helpful Docs if You Want to Go Deeper

Consult the version-appropriate Laravel deployment documentation, GitHub Actions documentation and WP-CLI reference. Check exact command behavior and environment scope rather than treating an example as a production-tested recipe.

Frequently Asked Questions

No. It can avoid a missing code-path window, but runtime paths, schema, sessions, caches, assets and queued payloads must stay compatible. Validate the candidate, coordinate workers and verify real HTTP and job outcomes after activation.

Attach the target environment and shared paths before application scripts. Keep bootstrap/cache and compiled views release-local. Stop and drain workers through their manager to prevent old-code auto-restart, then start them from the new path and verify release identity and job results.

Match relevant software and runtime behavior, while isolating credentials, databases, queues and writeable media. Use synthetic or sanitized data, sandbox payments and message sinks. Promote the tested artifact; do not copy staging data or config caches over production state.
↑