VPS Snaps

We earn commissions when you shop through the links below.

How to move a WordPress site to a new host

Moving WordPress to a new host means copying the site directory and the MySQL or MariaDB database, then pointing DNS at the new server. Lower the DNS TTL a day ahead, rehearse the copy, set up HTTPS and test through your hosts file; then freeze the old site, copy once more and switch DNS. If the domain stays the same, no URLs change. If it changes, rewrite them with wp search-replace, never with a text editor.

11 min readUpdated Checked against official documentation

The order of operations

This order keeps the old site serving until the new one has passed every test:

  1. A day or more ahead: lower the TTL of the domain's A and AAAA records to 300 seconds.
  2. Build the new server: same PHP major version and extensions, same database engine at the same or a newer version.
  3. Rehearse: copy, import, fix ownership, change URLs if the address changes.
  4. Set up HTTPS and test through your hosts file.
  5. Cutover: freeze the old site, copy once more, import, test again.
  6. Switch DNS, and keep the old host frozen until traffic to it stops.

Only step 5 and the DNS change affect visitors. TTLs, inventory and rollback are covered in moving a server to a new provider; this guide covers what is specific to WordPress. Commands run in a root shell (sudo -i), except WP-CLI, which refuses to run as root and runs here as the PHP user, www-data.

Still choosing a new host? A DigitalOcean Droplet is one option for a WordPress site you run yourself.

Create a DigitalOcean account

Affiliate link — we earn a commission if you sign up.

Copy the database and the files

On the old server, export with WP-CLI. - writes the dump to standard output, and --single-transaction is passed to mysqldump, which reads InnoDB tables from one snapshot without locking the site:

Terminal (old server)
sudo -u www-data wp --path=/var/www/example.com db export --single-transaction - | gzip > /root/example.com-move.sql.gz

On the new server, pull the site and the dump; the old server is only read. The leading / anchors the exclude, so only the top-level page cache is skipped (checked with rsync 3.2.7):

Terminal (new server)
rsync -a --info=progress2 --exclude=/wp-content/cache/ [email protected]:/var/www/example.com/ /var/www/example.com/
Terminal (new server)
rsync -a [email protected]:/root/example.com-move.sql.gz /root/

No SSH on the old host? Export from phpMyAdmin and download the files over SFTP. See copying files between servers for keys and resumable copies, and the WordPress backup guide for what each part of the site holds.

Import, keep wp-config.php, fix ownership

Create the database and user on the new server (run mysql as root for the prompt). Reusing the old name, user and password means wp-config.php works unchanged:

MySQL / MariaDB prompt
CREATE DATABASE wordpress;
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'same-password-as-before';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wp_user'@'localhost';

Otherwise edit DB_NAME, DB_USER, DB_PASSWORD and DB_HOST (shared hosts often use their own database hostname; yours is usually localhost). Leave the keys and salts alone: WordPress's docs say changing them invalidates all existing cookies. Keep them, the database and the domain, and logged-in users stay logged in. Import:

Terminal (new server)
gunzip -c /root/example.com-move.sql.gz | sudo -u www-data wp --path=/var/www/example.com db import -

wp db import does not create the database. Before the final import at cutover, wp db clean --yes drops every table with the site's prefix, so nothing created while you tested survives. Then give the files to the user PHP runs as:

Terminal (new server)
chown -R www-data:www-data /var/www/example.com

WordPress writes files directly only when a file it creates in wp-content has the same owner as its own files; otherwise it asks for FTP details whenever you update a plugin. The backup guide lists file modes.

Change URLs only if the address changes

WordPress stores full URLs in options, posts and plugin settings, some inside PHP serialized data that records each string's length. We serialized an option holding the old address with PHP 8.3, then ran sed over it:

option.txt, before and after sed
a:1:{s:4:"home";s:23:"https://old.example.com";}
a:1:{s:4:"home";s:23:"https://example.net";}

The string is now 19 characters but still claims 23, so unserialize() failed with Error at offset 18 of 44 bytes and returned false: whatever owns the value loses its settings. wp search-replace unserializes, replaces and serializes again. Preview first:

Terminal (new server)
sudo -u www-data wp --path=/var/www/example.com search-replace 'https://old.example.com' 'https://example.net' --all-tables --precise --skip-columns=guid --dry-run
FlagWhat it does
--dry-runReports replacements per table and column, ending Success: <count> replacements to be made.; saves nothing.
--all-tablesEvery table in the database, whatever its prefix. If the database also holds another app, use --all-tables-with-prefix.
--preciseEvery column goes through PHP. Without it, only columns where some value starts a:, i: or O: and a digit go through PHP; the rest get a plain SQL replace, which can miss serialized strings.
--skip-columns=guidLeaves post GUIDs alone, as WordPress's guide insists: feed readers use them to recognize posts.

Then run it without --dry-run. Do one replacement per change: the domain, http:// to https://, an old path such as /home/olduser/public_html. WP_HOME and WP_SITEURL in wp-config.php override the database; update them too.

Set up HTTPS before you switch

Let's Encrypt's usual HTTP-01 challenge fetches a file from your domain on port 80, so it works only once DNS points at the new server. To be ready before the switch, copy /etc/letsencrypt from an old server you ran (the migration guide shows how), or use DNS-01, which proves control with a TXT record at _acme-challenge.example.com. With Cloudflare DNS, save an API token limited to Zone:DNS:Edit on this zone in a file only root can read (chmod 600):

/root/.secrets/certbot/cloudflare.ini
dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567
Terminal (new server)
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini -d example.com -d www.example.com

certonly does not install the certificate: point the server block at /etc/letsencrypt/live/example.com/fullchain.pem and privkey.pem. Renewals reuse the plugin. Leftover http:// links to images and scripts cause mixed content warnings; the http:// to https:// replacement fixes them.

Behind a proxy that ends HTTPS and forwards plain HTTP, WordPress redirects forever. WordPress's HTTPS guide reads the proxy's X-Forwarded-Proto header in wp-config.php, above the stop-editing line; this version adds an isset check:

wp-config.php
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && strpos( $_SERVER['HTTP_X_FORWARDED_PROTO'], 'https' ) !== false ) {
	$_SERVER['HTTPS'] = 'on';
}

On Cloudflare, use Full (strict) with a certificate on the server: its docs name Flexible mode, which reaches your server over HTTP, as a cause of redirect loops when the server redirects to HTTPS.

Test through your hosts file

WordPress redirects every request to its stored address, so browsing the new server by IP lands you on the live site. Instead, point the real name at the new server on your own computer, in /etc/hosts (on Windows, C:\Windows\System32\drivers\etc\hosts):

/etc/hosts (your computer)
198.51.100.20 example.com www.example.com

Avoid preview domains: setting WP_HOME and WP_SITEURL to one leaves URLs inside posts pointing at the live site, so images load from the old server and hide uploads that never arrived. Check the home page, a post, the login, the Media Library, a form or checkout, and the browser console. curl needs no hosts file:

Terminal (your computer)
curl -sS -o /dev/null -w '%{http_code}\n' --resolve example.com:443:198.51.100.20 https://example.com/sample-page/

Remove the hosts entry afterwards, or you will think DNS has switched for everyone.

Cutover: freeze, copy once more, switch DNS

Freeze the old site so no order or comment lands there after the final export. WordPress shows its maintenance page while a .maintenance file in the site root holds a timestamp under 10 minutes old, which is why wp maintenance-mode activate lapses after 10 minutes. This file's timestamp is always now, so the freeze lasts until you delete it:

Terminal (old server)
echo '<?php $upgrading = time(); ?>' > /var/www/example.com/.maintenance

Visitors get a 503 saying Briefly unavailable for scheduled maintenance. Check back in a minute. Export the database again, pull the dump, and copy what changed:

Terminal (new server)
rsync -a --delete --exclude=/wp-content/cache/ --exclude=/.maintenance --exclude=/wp-config.php [email protected]:/var/www/example.com/ /var/www/example.com/

--delete removes files deleted since the rehearsal (preview with -n -i, as the migration guide shows). Excluded files are safe from it, so the new wp-config.php survives and .maintenance stays behind. Run wp db clean --yes, import, repeat any search-replace, flush caches, test. Then switch the A record, and AAAA if there is one, or IPv6 visitors keep reaching the old host:

Terminal
dig +short example.com A @1.1.1.1

Keep the old host frozen until its access log goes quiet and the new server's backups have run, a week at least. Take a last backup, then cancel. Raise the TTL again.

Email does not move with the site

Mailboxes live wherever the MX records point, often at the old shared host, so cancelling it ends your email: move mail first. Changing DNS hosts too? Recreate every record, including MX and the SPF, DKIM and DMARC TXT records; a DNS backup lists them. Update an SPF record that names the old IP.

WordPress sends with PHP's mail() by default, which its docs say needs a sendmail-compatible mail server on the machine. A fresh server usually has none, so password resets and order emails quietly stop. Relay through a mail service with a local MTA or an SMTP plugin, after checking the provider's ports: DigitalOcean blocks SMTP ports 25, 465 and 587 on Droplets by default. Test:

Terminal (new server)
sudo -u www-data wp --path=/var/www/example.com eval 'var_dump( wp_mail( "[email protected]", "Test from the new server", "It works." ) );'

bool(true) means handed to the mailer, not delivered: check the inbox.

Caches, CDN and multisite

  • Page cache: clear the caching plugin's cache after the import.
  • Drop-ins: WordPress loads wp-content/object-cache.php whenever it exists. If it talks to Redis or Memcached, start that service on the new server or delete the drop-in, then run wp cache flush. Check wp-content/mu-plugins for code the old host added for its own platform.
  • CDN: point the origin at the new server, and purge the CDN after the switch.

A multisite network on the same domain moves like a single site. A new domain needs more: wp_site and wp_blogs store the bare host name, not a URL, and wp-config.php defines DOMAIN_CURRENT_SITE. Replace the bare name while wp-config.php still has the old one; --url picks the site WP-CLI loads, and --network covers every site's tables:

Terminal (new server)
sudo -u www-data wp --path=/var/www/example.com search-replace 'old.example.com' 'example.net' --url=old.example.com --network --precise --skip-columns=guid --dry-run

The bare name also matches email addresses, so read the dry run. Then update DOMAIN_CURRENT_SITE and review wp_site and wp_blogs by hand, as WordPress advises. Subdomain networks need wildcard DNS and a wildcard certificate, which Let's Encrypt issues only through DNS-01.

Common errors after a move

SymptomCause and fix
Error establishing a database connectionWrong DB_NAME, DB_USER, DB_PASSWORD or DB_HOST for this server, no grant, or MySQL not running. On multisite it also appears when no network matches the domain.
ERR_TOO_MANY_REDIRECTS or The page isn’t redirecting properlyA proxy ends HTTPS (see above), or the web server redirects to www while WordPress's address has none, or the reverse: WordPress redirects between the two to match its own address.
The home page works, other pages are 404The web server's own 404 means rewrites never reach WordPress: on Apache run a2enmod rewrite and set AllowOverride All (the default, None, ignores .htaccess); on nginx add try_files $uri $uri/ /index.php?$args; to location /. The theme's 404 means stale rules: run wp rewrite flush.
Briefly unavailable for scheduled maintenance. Check back in a minute.A fresh .maintenance file came across. Delete it.
Your PHP installation appears to be missing the MySQL extension which is required by WordPress.Install php-mysql (Debian and Ubuntu) and restart PHP-FPM.
To perform the requested action, WordPress needs to access your web server.Files are not owned by the user PHP runs as. Fix them with chown.
Unknown collation: 'utf8mb4_0900_ai_ci' on importA MySQL 8 dump into MariaDB before 11.4.5 or MySQL 5.7: see importing a SQL file.
Images load from the old domainRun search-replace for it, check theme files for hard-coded links, clear the page cache.

Checklist

wordpress-move-checklist.md
# WordPress move: example.com
[ ] TTL of A and AAAA at 300, one old TTL before cutover
[ ] Know where MX, SPF, DKIM, DMARC and mailboxes live
[ ] New server: same PHP major version and extensions, same DB engine
[ ] Rehearsal: copied, imported, ownership fixed, URLs replaced if needed
[ ] Valid HTTPS certificate on the new server
[ ] Hosts-file test: home, a post, login, media, forms, checkout
[ ] Test email sent and received from the new server
[ ] DISABLE_WP_CRON set? System cron job added on the new server
[ ] Cutover: .maintenance, wp db clean, final export, import, rsync --delete
[ ] A and AAAA switched, dig shows the new IP, hosts entry removed
[ ] Backups running on the new server, one restore tested
[ ] TTL raised; old host cancelled after a week and a final backup

Frequently asked questions

How long does it take to move a WordPress site to a new host?
The copy takes as long as your data needs, while the old site keeps serving. Downtime is the final export, copy and import, usually minutes, plus up to one DNS TTL for cached lookups.
Do I need to run search-replace if the domain stays the same?
No. If the domain, protocol and path stay the same, the stored URLs are already right. Run it only for what changed, such as http to https or an old filesystem path.
Will moving WordPress log my users out?
Not if you keep wp-config.php's keys and salts and the same domain. New keys and salts invalidate every login cookie, and on a new domain browsers send no cookies at all.
Does moving my website move my email?
No. Email follows the domain's MX records. If they point at the old host, cancelling it ends your mailboxes, so move mail first or keep it there.

How this was checked

Commands, limits and prices were checked against these official pages, on October 4, 2026: