Skip to content

How to Migrate an Existing WordPress Site to Bedrock Without Downtime

Emnes
Illustration showing migration arrow from vanilla WordPress to Bedrock

Most WordPress sites were built before Bedrock existed, or built without Bedrock because the team adopted it later. If your existing site is on vanilla WordPress — wp-config.php committed to Git, plugins installed through the admin, no composer.json in sight — and you want to move to Bedrock, you don’t have to start from scratch. You can migrate an existing WordPress site to Bedrock with zero downtime, preserving every post, every user, every media file, every plugin, and every setting.

This guide is a complete, step-by-step migration playbook. It’s written for a real scenario — a live site that you can’t take down for a weekend — and covers the cases most tutorials gloss over: premium plugins without WordPress.org downloads, custom mu-plugins, plugin-generated database tables, media libraries in the tens of thousands of files, and the inevitable stray wp-content/ hardcoded paths. Expect to spend a full day end-to-end on a medium-complexity site, and plan multiple staging rehearsals before the cutover.

If Bedrock is still a black box to you, start with our Bedrock complete guide first. This post assumes you understand the directory structure and Composer workflow.

Before You Start: Is Migration Worth It?

Bedrock is an investment. Before committing to migration, verify that the return justifies the work:

  • You ship updates regularly. If the site changes once a year, the Composer + .env workflow is overkill.
  • You have or plan to have multiple developers. Solo devs can survive without Bedrock; teams struggle without it.
  • You want staging/production parity. Bedrock’s per-environment config is a major quality-of-life improvement for anyone running a real staging site.
  • You care about security posture. Moving secrets out of Git and disabling admin-level file edits eliminates an entire class of incidents.
  • Your host supports Composer and SSH. Bedrock needs server-side Composer; cheap shared hosts without SSH can’t run it.

If at least two of those apply, migration is worth it. If none do, stay on vanilla WordPress and read a different article.

The Migration Workflow in Ten Steps

  • 1. Inventory the current site — plugins, themes, mu-plugins, custom code, server PHP version.
  • 2. Take a full backup (files + database) and verify you can restore it.
  • 3. Spin up a fresh Bedrock project locally.
  • 4. Port every plugin to composer.json.
  • 5. Port the theme.
  • 6. Port mu-plugins and custom code.
  • 7. Import the production database and run search-replace on URLs.
  • 8. Verify the site works locally end-to-end.
  • 9. Deploy Bedrock to a staging server and rehearse the cutover.
  • 10. Cut over production DNS or document root, monitor for 24 hours.

Let’s walk through each step in detail.

Step 1 — Inventory the Current Site

Before touching anything, document what you have. Run these commands on the current server (or use the WordPress admin if you don’t have SSH):

wp plugin list --format=csv --fields=name,status,version,update
wp theme list --format=csv --fields=name,status,version
wp core version
php -v
ls -la wp-content/mu-plugins/

Save the output. You’ll work through it methodically during the port, and forgetting a single plugin or mu-plugin is the most common cause of post-migration bugs.

For each plugin, classify it:

  • Free on WordPress.org — install via WP Packages or WPackagist.
  • Premium with a vendor-supported Composer repository (ACF Pro, Gravity Forms) — needs vendor repo + license key in auth.json.
  • Premium without a Composer repository — needs a private Composer repository, Satis, or a zip-URL package repository.
  • Custom or abandoned — will live in web/app/plugins/ as part of your Bedrock project or a private Composer package.

Step 2 — Full Backup and Restore Test

This is not optional. Take a full backup of files and database, and verify you can restore it on a scratch server. A backup you haven’t tested is a guess, not a safety net.

Our Backup Migrate Reset plugin handles this workflow. Other solid options: UpdraftPlus, Duplicator, or a hand-rolled mysqldump + tar of the wp-content directory. Back up the full filesystem (not just wp-content/) so you have a reference for files you might have forgotten about.

Step 3 — Spin Up a Fresh Bedrock Project

On your local machine:

composer create-project roots/bedrock my-site
cd my-site
cp .env.example .env

Edit .env with local database credentials, a local site URL (http://my-site.test or similar), and fresh auth keys. The auth keys will change during migration — you’ll regenerate them or copy from production depending on whether you want existing user sessions to survive the cutover (usually not a concern).

Create the local database and point your local web server (nginx, Apache, or PHP’s built-in server) at web/. The Bedrock WordPress installer should load.

Step 4 — Port Every Plugin to composer.json

Go through the plugin inventory from Step 1, and for each plugin add it to composer.json:

composer require wp-plugin/wordpress-seo:^22.0
composer require wp-plugin/wp-mail-smtp:^4.3
composer require wp-plugin/redis-cache

Pin to major versions with ^ where possible. Note the exact version from the source site — you want to match versions during migration so plugins behave identically. You can let Composer pick newer versions on a post-migration update pass.

For premium plugins, add the vendor’s Composer repository to composer.json:

"repositories": [
  { "type": "composer", "url": "https://composer.advancedcustomfields.com" }
]

And your license key in auth.json (never commit this to Git):

{
  "http-basic": {
    "connect.advancedcustomfields.com": { "username": "YOUR_LICENSE_KEY", "password": "example.com" }
  }
}

Run composer install after each batch of additions and verify the plugin shows up in web/app/plugins/.

Step 5 — Port the Theme

Themes fall into three buckets:

  • WordPress.org themes (Twenty Twenty-Five, Astra free, GeneratePress free) — install via composer require wp-theme/twentytwentyfive.
  • Premium themes — use the vendor’s Composer repository or put the theme in a private Composer repository.
  • Custom themes — copy the theme directory into web/app/themes/your-theme/ and either commit it to your project repo or package it as a private Composer package.

If you’re migrating a custom theme and planning to move to Sage, do that as a separate migration — don’t combine “move to Bedrock” and “rewrite the theme” into one cutover. Bedrock migration first, Sage rewrite later.

Step 6 — Port Mu-Plugins and Custom Code

Copy everything in wp-content/mu-plugins/ to web/app/mu-plugins/. Bedrock’s autoloader handles the main-file autoloading automatically, so plugins in mu-plugins/ that aren’t in a subdirectory work without changes.

Watch for custom code in:

  • wp-content/mu-plugins/ — standard.
  • wp-content/plugins/custom-plugin/ — if anyone wrote a site-specific plugin, it needs to move to web/app/plugins/ (or become a private Composer package).
  • wp-content/themes/your-theme/functions.php — ships with the theme. No change.
  • wp-config.php — custom constants or code at the end of wp-config.php need to move to config/application.php or config/environments/*.php. Bedrock’s wp-config.php should stay unmodified.

This step is where forgotten code hides. Diff the old wp-config.php against Bedrock’s defaults and account for every custom line.

Step 7 — Import the Database and Run Search-Replace

Export the production database:

wp db export production.sql

Import it into your local Bedrock database:

wp db import production.sql

Two search-and-replace passes are essential:

  • Production URL → local URLwp search-replace 'https://example.com' 'http://my-site.test' --all-tables. This rewrites serialized data correctly, which a raw SQL find-and-replace would corrupt.
  • wp-content paths → app paths, if any are stored in the database. Most plugins store relative paths, but some (particularly premium plugins with their own media handling) store absolute paths: wp search-replace '/wp-content/' '/app/' --all-tables --dry-run first, then without --dry-run.

Don’t skip the --dry-run. A wrong search-replace can break serialized option data irreversibly.

Step 8 — Verify Everything Works Locally

Before pushing anything to a staging server, walk through the local site end-to-end:

  • Log in to the admin with an existing user.
  • Visit a post, a page, and a custom post type — check URLs, permalinks, and template rendering.
  • Check media — do images load? Are featured images set? Are uploaded PDFs accessible?
  • Test every plugin’s main feature — form submissions, WooCommerce checkout, search, SEO meta output.
  • Check debug.log — any PHP warnings or errors point to plugin-path issues or missing dependencies.
  • Run a quick accessibility and broken-link scan with a tool like Dead Link Checker.

Fix issues locally. Every bug you leave for staging multiplies the cutover risk.

Step 9 — Deploy to Staging and Rehearse the Cutover

Deploy the Bedrock project to a staging server that mirrors production (same PHP version, similar memory limits, similar caching). Run the same end-to-end test you did locally. If you have a Trellis setup, this is a single trellis deploy staging example.com.

Migrate media files separately: rsync wp-content/uploads/ from production into the staging server’s web/app/uploads/ directory. For very large media libraries, consider offloading to S3 or Cloudflare R2 before the cutover.

Rehearse the cutover exactly as you’ll do it in production:

  • Put production in maintenance mode.
  • Take a final database dump from production.
  • Import into staging (your final Bedrock environment).
  • Run search-replace for the production URL.
  • rsync the latest uploads/.
  • Flip traffic.
  • Verify, then remove maintenance mode.

Time this rehearsal. A well-planned cutover should take 15–30 minutes of downtime for a medium-sized site. If your rehearsal takes longer, find the bottleneck (usually database import or media sync) before the real cutover.

Step 10 — Production Cutover

Two common patterns for zero-downtime cutover:

Pattern A — DNS Cutover

Put the Bedrock version on a new server at a staging domain, verify, then switch DNS to point example.com at the new server. Downtime is zero for most visitors; some will hit cached DNS for up to your TTL. Pre-lower the TTL 48 hours before cutover (to 60 seconds) so DNS propagation is fast on the day.

Pattern B — Document-Root Switch

Deploy Bedrock alongside the old WordPress install on the same server, under a different path. Verify via hosts-file override or a preview URL. When ready, switch the nginx document root from /var/www/old-wordpress/ to /var/www/bedrock/web/ and reload nginx. Downtime is the length of one nginx reload — effectively zero.

After cutover:

  • Monitor error logs for the first 24 hours. Set up alerts for PHP fatal errors and 500s.
  • Verify cron runs (wp cron event list should match production).
  • Check search engines — submit a sitemap, verify Google Search Console shows no new 404s.
  • Keep the old install available, read-only, for a rollback window — 72 hours is typical.

Handling the Hard Cases

Very Large Media Libraries (100K+ files)

rsync over a single network link is slow. Options:

  • Offload to S3 or R2 before migrating with a plugin like WP Offload Media. Then the Bedrock site reads from S3 directly — no file sync needed.
  • rsync incrementally for days before cutover, then do a final incremental sync at cutover time.
  • Use a disk snapshot or volume attach on your cloud provider if both servers are in the same region.

WooCommerce Sites

WooCommerce adds order data, customer data, and session state that can’t afford downtime. Use a maintenance window during off-peak hours (3 AM Sunday for most US-focused stores). Disable new orders during cutover by adding a short-term maintenance plugin. Always do a final database dump at the actual cutover moment, not an hour earlier.

Plugins That Hardcode wp-content/

If testing reveals a plugin that breaks under Bedrock’s directory structure, you have three options: patch the plugin, contact the vendor and wait for a fix, or replace the plugin with an alternative. For widely-used plugins, a patch in a composer.json post-install script can bridge the gap until the vendor ships a fix.

Plugins with Their Own Database Tables

These carry over automatically with the database export. Verify after migration that the plugin’s “Status” page shows all its tables present — a missed table during import breaks silently.

Post-Migration Cleanup

Once the site is stable on Bedrock, spend a sprint on cleanup:

  • Remove the old WordPress install from the server after the rollback window.
  • Lower auto-update-related plugins — you’re now updating via Composer; disable internal auto-updaters.
  • Add WP Sec Adv to composer.json and run composer audit.
  • Set up CI — GitHub Actions or similar — to run composer install and tests on every PR.
  • Document the workflow for the team: how to add a plugin, how to update, how to deploy.
  • Consider Trellis for automated deploys if you’re not already using it.

Frequently Asked Questions

How long does a Bedrock migration take?

For a medium-complexity site (20–30 plugins, a custom theme, a few GB of media): plan 1–2 full working days for preparation, staging, and rehearsal. The actual cutover should be under 30 minutes of downtime with a good rehearsal.

Can I migrate a WooCommerce store to Bedrock?

Yes. WooCommerce itself is fully compatible with Bedrock. The challenge is timing the cutover to minimize customer-facing disruption. Plan for an off-peak maintenance window, rehearse thoroughly, and keep a rollback path ready.

What if a plugin doesn’t work after migration?

Check the plugin against the “hardcoded wp-content/” issue first. Next, check plugin-specific database tables were imported correctly. If the plugin truly doesn’t work under Bedrock, either patch it, contact the vendor, or replace it. Track the issue — it’s rare, but when it happens you want it documented.

Do I need to migrate media files separately?

Yes. Media files aren’t in the database — they’re on disk in wp-content/uploads/. You’ll need to rsync them to the new server’s web/app/uploads/ directory, or offload to S3/R2 before cutover.

Should I update plugins during migration or after?

After. Match plugin versions exactly during migration so you’re comparing like-for-like. Once the site is stable on Bedrock, run a post-migration update pass in a controlled way with composer update.

What’s the rollback plan if migration fails?

Keep the old WordPress install available and read-only on the old server for at least 72 hours. If cutover reveals a blocking problem, switch DNS (or nginx document root) back to the old install and debug without time pressure. The backup from Step 2 is your ultimate fallback.

Migrate Once, Reap the Benefits Forever

A Bedrock migration is real work — a day or two of focused effort, plus the discipline to maintain the Composer workflow afterward. In exchange, you get deterministic dependencies, proper staging parity, cleaner Git history, a stronger security posture, and a foundation that scales to multiple developers and frequent deploys.

At Emnes, we’ve migrated dozens of sites from vanilla WordPress to Bedrock. The playbook in this post is derived from those migrations. If you want help planning a migration or want a second pair of eyes on a rehearsal, get in touch.

Related reading: the Bedrock complete guide, WordPress backup strategy for 2026, and migrating WordPress without downtime (generic, non-Bedrock version).