There are plugins that automate WordPress migration entirely. One click, everything moves, done in ten minutes. I've used them. They work — until they don't, and when they fail you're staring at a broken site with no clear idea what went wrong.

My process is manual. It takes the same ten to fifteen minutes. The difference is I know exactly what happened at every step.

The setup before migration starts

Good migration starts before you touch any files. On the target host I set up a clean, fresh WordPress install first — same WordPress version, same PHP version where possible. This gives me the correct file paths, the correct database prefix, and a working baseline to import into.

I use the same database name, username, and password as the local environment where I can. One less variable to troubleshoot if something goes wrong.

For hosting I recommend SiteGround for clients who need a recommendation. It's slightly more expensive than the budget options but the performance is reliable and the support is good — which means fewer calls from clients six months after delivery. That said I can work with any host. All I need is SSH access and a way to access the database, whether that's phpMyAdmin, a terminal, or anything else. Agencies almost always have their own hosting setup and I work with whatever they have.

Migrating the database

I use WP Migrate — the free tier — for the database. It does one thing well: exports a clean SQL file with search-replace already applied.

The process is straightforward. In WP Migrate on the local site I export the database and run find-and-replace at the same time — swapping the local URL for the live URL, and the local file paths for the server paths. The exported SQL file lands on my machine ready to import.

On the target host I drop all existing tables from the fresh WordPress install — this removes the default data from the clean install — and import the SQL file from local. The database now contains all the content, settings, users, and options from the local build, with the correct URLs and paths for the live environment.

This is the step where automated migration plugins save the least time. The export and import takes two to three minutes either way. The difference is that with WP Migrate I can see exactly what was replaced and verify it before importing. With an automated plugin the search-replace happens invisibly and if a URL doesn't get caught you find out later when something is broken.

Moving the files

For files I use rsync over SSH. Three folders move from local to server: wp-content/themes, wp-content/plugins, and wp-content/uploads.

rsync -avz --progress ./wp-content/uploads/ user@server:/path/to/wp-content/uploads/

For small to medium upload folders I transfer them directly. For large folders — anything over a few hundred MB — I archive first, transfer the archive, and extract on the server. Transferring one file is faster and more reliable than transferring thousands of small files, especially images.

# Archive locally
tar -czf uploads.tar.gz wp-content/uploads/

# Transfer
rsync -avz --progress uploads.tar.gz user@server:/path/to/

# Extract on server
tar -xzf uploads.tar.gz

The themes and plugins folders are usually small enough to transfer directly without archiving.

I don't use FTP for this. FTP works but it's slow — moving a large uploads folder over FTP can take hours. Rsync over SSH moves the same data in minutes.

After the import

Once the database is imported and files are in place a few quick checks before calling it done:

  • Log into the WordPress admin and confirm everything loads
  • Check Settings → General — confirm the site URL and WordPress URL are correct
  • Visit the front end and click through the main pages
  • Test any forms end to end — submission, confirmation, email delivery
  • Check the uploads — images loading correctly, no broken media
  • Flush permalinks — Settings → Permalinks → Save Changes — this rebuilds the .htaccess rewrite rules which sometimes breaks after migration if not flushed

The permalink flush catches more post-migration issues than anything else. It's the first thing I check if a page returns a 404 after moving.

DNS and SSL

DNS is usually handled by the client or the agency — they point the domain to the new host and SSL is provisioned automatically by the host or Coolify if it's a VPS setup.

If I'm handling it myself the process is: update the A record to point to the new server IP, wait for propagation (usually fast on Cloudflare, slower on other registrars), confirm SSL is active. Most modern hosts provision Let's Encrypt SSL automatically once the domain resolves.

One thing worth noting: test the site on the server IP or a temporary URL before pointing DNS. That way you confirm everything works before the domain switches over and the old site goes down.

The handoff

For direct clients I put together a short PDF — login credentials, how to edit pages, how to update the menu, how to add a blog post, who to contact for support. Nothing technical, just what they need to manage the site day to day. A Loom video walkthrough of the admin is even better — five minutes of video replaces hours of support emails.

For agencies the handoff is whatever they need. Usually that means staging and live credentials, a note on what plugins are installed and why, and anything non-standard about the build that the next developer should know. Agencies have their own processes and I fit into them rather than imposing mine.

Why manual over automated

The automated migration plugins — All-in-One WP Migration, Duplicator, others — work fine on straightforward migrations. The problem is when they don't work. The failure mode is opaque: something is broken, the plugin did something at some point, and you're searching through logs trying to reconstruct what happened.

With a manual process the failure mode is transparent. If the rsync errors out I see it immediately. If the database import fails I see the exact error. If the search-replace missed something I can run it again. Every step is visible and every problem is diagnosable.

The time difference is negligible — ten to fifteen minutes either way. The difference is confidence. I know the site is on the live server exactly the way it was on local, and I know that because I moved every piece of it myself.