A WordPress site that still depends on PHP 5.6 does not need a compatibility museum. It needs a recovery plan.

That distinction matters. A conventional migration asks, “How do we copy this site to another server?” An end-of-life recovery asks a harder question: “How do we preserve the content and design without carrying unsupported software, abandoned dependencies, and old security assumptions into the new environment?”

We recently worked through that problem on a long-running WordPress site. The source was on PHP 5.6. The staging target was PHP 8.3 and WordPress 7. We did not install an intermediate PHP release, and we did not lower the target runtime to make obsolete code feel comfortable. We moved to the supported baseline, then dealt with each incompatibility where it belonged: in the application.

Here is the workflow that made the migration controlled, testable, and reversible.

Why a Straight File Copy Is the Wrong Starting Point

Copying the document root and importing the database may make a site appear on a new server, but it can also reproduce every old problem: outdated WordPress core files, vulnerable inactive plugins, abandoned vendor libraries, hard-coded paths, stale SMTP credentials, and server-specific configuration.

It can also overwrite the parts of a managed platform that should remain managed, such as its WordPress configuration, security controls, caching integration, PHP process configuration, and platform plugins.

Our rule was simple: the new environment remained the platform baseline. We migrated the site’s content, uploads, database, current theme, and required application components into that baseline. We did not turn the target into a clone of the old server.

Step 1: Inventory Before You Update Anything

Version numbers are only the first layer of a useful inventory. For every plugin and theme, we recorded:

  • The installed version and whether it was active.
  • Whether a maintained update existed from a trusted source.
  • Whether the site actually used the component.
  • What content, tables, scheduled tasks, shortcodes, widgets, or external services depended on it.
  • Whether it should be updated, replaced, repaired, removed, or retained.
  • How we would prove the decision was safe.

This is where active-versus-used becomes important. An active plugin can be doing nothing. An inactive plugin can still expose vulnerable code. A private add-on can look harmless until you discover it controls a content type or scheduled import.

The inventory is also the commercial record for recovery work. A standard migration is a copy. Recovery may include component updates, compatibility testing, data cleanup, configuration repair, and feature validation. Recording each touch makes the scope visible before it becomes a surprise.

Step 2: Build a Rollback You Can Prove

We created a protected source backup of the site files, database, and DNS state, then verified its checksums. We created another restore point on the destination before any destructive cleanup.

The source site stayed online and unchanged while staging was tested. Public DNS did not move. That gave the project a simple rollback path: if the staging build failed, visitors were still using the original site.

This is one reason we separate four approvals that are often blurred together:

  1. Approval to build staging.
  2. Approval to remove obsolete code and data.
  3. Approval to cut over the public domain.
  4. Approval to delete the old hosting account after the soak period.

A successful staging page is not permission to take all four actions.

Step 3: Update Maintained Code Before Judging Compatibility

The first PHP 8.3 lint pass found syntax failures inside old bundled libraries. That result did not automatically mean the site needed custom development. It meant the deployed copies were old.

We updated packages that still had a trusted public release channel, then repeated the scan. From the WordPress root, a compact lint pass looks like this:

find wp-content -type f -name '*.php' -print0 \
  | xargs -0 -n1 php -l 2>&1 \
  | grep -v 'No syntax errors detected'

Zero output is the syntax pass condition. In this case, maintained updates eliminated the initial parse failures.

That is an encouraging result, but syntax is only one gate. PHP can parse a file that still fails when WordPress calls a removed function, loads a conflicting class, or runs an old database upgrade routine.

Step 4: Test Runtime Behaviour One Component at a Time

We captured a clean PHP error-log baseline, loaded each required plugin individually, requested the homepage and the feature routes that mattered, and compared the new log entries with the baseline.

This makes failures attributable. If ten plugins are updated and activated at once, a fatal error tells you only that something broke. If components are introduced one by one, the error has an owner and a rollback point.

We used the same discipline for database upgrades. A one-time schema warning can mean an upgrade routine found an object that already existed and then completed successfully. It can also mean every request is retrying a broken migration. The difference is visible only when you clear the baseline and repeat the test.

If a plugin update leaves you with a white screen or fatal error, our WordPress plugin conflict workflow shows how to isolate the failing component without exposing errors to visitors.

Step 5: Treat Unsupported and Unused Code as a Decision

The hardest dependencies were not the components with obvious public updates. They were private add-ons with no trusted update channel and old tools whose settings remained in the database even though the site no longer used their features.

Our recovery policy is:

Finding Action Proof
Maintained update exists Update on staging Version record, feature test, clean log delta
Required but abandoned Replace or repair The real user flow passes
Unused Back up, then remove code and orphaned data No content, task, table, option, or route depends on it
Requires end-of-life PHP Replace or remove Site passes on the supported runtime

“It was active on the old site” is not proof that it belongs on the new one. “The homepage still loads” is not proof that deletion was safe. The evidence has to match the feature.

Step 6: Preserve the Design When the Theme Is Still Supportable

An old theme version does not always require a redesign. In this recovery, the existing theme had a maintained release that worked on the current runtime. Updating it preserved the site’s familiar layout while removing the need to keep obsolete PHP.

We compared the source and staging site at desktop and mobile widths. Navigation, typography, content grids, and responsive behaviour closely matched. That visual check mattered because HTTP 200 responses cannot tell you that a menu wrapped incorrectly or a mobile layout collapsed.

Before replacing theme files, compare the deployed copy with its upstream package. Custom edits, a child theme, Customizer settings, menus, and widgets can all carry design state that a blind reinstall would erase.

Step 7: Test the Whole Site, Not Just the Homepage

Our staging acceptance included published pages, sampled media, admin access, plugin feature routes, redirects, caching, security controls, backups, and a clean PHP-log delta. We also reconciled WordPress object counts so a visually convincing homepage could not hide missing content.

The exact checklist changes by site. A store needs checkout and payment tests. A membership site needs login and access-control tests. A publishing site needs archives, feeds, search, scheduled posts, and media. Inventory tells you which paths matter.

Email deserves its own boundary. A website move should not silently become a mailbox migration or a nameserver change. Preserve MX and related records, and change only the web records needed for cutover unless the email owner approves something broader.

Cutover Is a Release, Not the End of the Project

Once staging is accepted, take a fresh backup, capture the current DNS values, perform any final content sync, and change only the approved web records. Verify DNS from more than one public resolver, allow the new HTTPS certificate to attach, and repeat the public URL and error-log checks on the real domain.

Then soak the site. Watch availability, certificate state, scheduled jobs, redirects, caching, backups, PHP errors, and traffic signals. Keep the source account intact during that period.

Deleting the old account is a separate, explicit action. Before it happens, prove that public traffic no longer reaches the source, that no required website data exists only there, that the final backups are restorable, and that the account does not contain email data someone still expects to keep.

The Result: Current Software Without an Unplanned Redesign

The staging result ran the existing design on PHP 8.3 and WordPress 7, with maintained components updated and compatibility checked on the target runtime. The exercise reinforced a useful lesson: ending an EOL dependency does not always require rebuilding the website. It does require knowing what the website actually depends on.

If your WordPress site is stuck on an old PHP version, start with an inventory rather than a version switch in production. Canadian Web Hosting’s Managed WordPress migration review determines whether a site is ready for a standard migration or needs scoped remediation. That gives you a supported destination and a clear record of the recovery work instead of another temporary exception.