A WordPress site is four things: the WordPress core files, the wp-content folder with your themes, plugins and uploads, a few configuration files, and a database that holds every post, page, user, order and setting. Moving a site by hand means copying all four to the new server, pointing the copy at its new database and, if the address changes, rewriting the old address wherever WordPress stored it.
Most of the work is copying. Most of the trouble comes from two steps: changing the address without breaking saved settings, and switching DNS without losing the orders or comments that arrive in between. This guide covers two routes, a hosting control panel with SFTP and phpMyAdmin, and the command line with SSH, mysqldump, rsync and WP-CLI. Use whichever your hosts allow; the steps are the same.
What you are moving
Before you copy anything, list what the site is made of. Some of it lives outside the WordPress folder, and a copy of the web root alone leaves it behind.
| Part | Where it lives | How you move it |
|---|---|---|
| WordPress core | wp-admin, wp-includes and the PHP files in the web root | Copy it, or install the same WordPress version on the new host |
| Themes, plugins and uploads | wp-content | Copy all of it, including mu-plugins and any folder a plugin created |
| Configuration | wp-config.php, .htaccess, .user.ini or php.ini, robots.txt | Copy, then edit for the new server |
| Database | MySQL or MariaDB on the old server | Export to a .sql file and import it |
| Server settings | DNS and mail records, cron entries, HTTPS certificate, nginx rules | Recreate by hand on the new host |
Three things are easy to miss. WordPress lets wp-config.php live one folder above the web root, so look there if it is not next to wp-admin. A folder inside wp-content can be a symlink to another path on the server, and copying the link without its target leaves an empty folder. And if the old server ran WordPress’s scheduled tasks from a server cron entry instead of on page visits, that entry does not travel with the files.
Before you start
- Take a full backup and keep it off both servers. Download the files and a database export to your own computer or storage, then check both: open the archive and confirm it lists your files, and check that the export is complete (step 1 shows how).
- Compare versions. In wp-admin, Tools > Site Health > Info shows the PHP, database server and WordPress versions. Give the new host the same PHP version, or a newer one that your theme and plugins support. A jump of several PHP versions during the move makes any error harder to trace, so move first and upgrade PHP afterwards.
- Lower the DNS TTL. At your DNS provider, set the TTL of the site’s A and AAAA records, and the www record, to 300 seconds. Do it at least one old TTL before the switch: if the old value was 86400 seconds, that means a day ahead, because resolvers that cached the old record keep it until it expires.
- Plan a content freeze. Anything written to the old database after your final export, such as a comment, a form entry or an order, stays on the old server. On a shop, pick a quiet hour for the final copy and put the old site into maintenance mode (step 8 shows how).
- Collect access. You need SFTP or SSH for both servers, database credentials on the new host, and logins for your domain registrar and DNS provider.
- Check disk space. The new account needs room for the files, the database, and a copy of the export while you import it. The uploads folder is often the largest part.
Step 1: Export the database
The old site’s wp-config.php holds the database details: DB_NAME, DB_USER, DB_PASSWORD and DB_HOST. Note the $table_prefix line as well, because step 5 needs it.
With phpMyAdmin
- Open phpMyAdmin from the old host’s control panel and select the database from DB_NAME in the left column.
- Open the Export tab and choose the Custom method.
- Select all tables. If the database also holds tables from another application, select the ones that start with your table prefix.
- Under the object creation options, tick the option that adds DROP TABLE statements. The import then replaces any table of the same name on the new server.
- Set compression to gzipped and start the export.
phpMyAdmin runs inside PHP, so a large database can hit the PHP time limit and produce a file that stops partway. Search the downloaded file for the CREATE TABLE line of the last table in phpMyAdmin’s list. If it is missing, or the export keeps failing, use the command line.
From the command line
Over SSH on the old server, run:
mysqldump --single-transaction --quick --default-character-set=utf8mb4 \
--no-tablespaces -u DB_USER -p DB_NAME | gzip > ~/site-db.sql.gz
Replace DB_USER and DB_NAME with the values from wp-config.php; mysqldump asks for the password. If DB_HOST is something other than localhost, add -h followed by its value.
--single-transactiontakes a consistent copy of InnoDB tables without locking the site while the dump runs.--quickstreams rows one at a time instead of loading each table into memory.--default-character-set=utf8mb4keeps accented letters and emoji intact.--no-tablespacesavoids the “Access denied; you need (at least one of) the PROCESS privilege(s)” error that MySQL 8 gives an ordinary hosting account.
If WP-CLI is installed, wp db export ~/site-db.sql run from the WordPress folder does the same and reads the credentials from wp-config.php for you. Both tools end the file with a line that starts -- Dump completed. A file without that line was cut short.
Write the export to your home folder, never inside the web root. A .sql file in public_html can be downloaded by anyone who requests its address, and it contains every user’s email address and password hash.
Step 2: Copy the files
Over SFTP
Connect to the old server with an SFTP client and download the whole WordPress folder, usually public_html, htdocs or www. Turn on the client’s option to show hidden files first, or .htaccess and .user.ini stay behind. Then upload the folder to the web root on the new host.
Thousands of small files transfer slowly over SFTP. If the old host’s file manager can compress a folder, zip the WordPress folder there, download one archive, upload it, and extract it with the new host’s file manager.
Server to server with rsync
With SSH on both servers, you can leave your own computer out of it and copy directly. Run this on the new server, with the old server’s address and path:
rsync -avz --exclude 'wp-content/cache/' \
[email protected]:/var/www/example.com/ /var/www/example.com/
The trailing slash on the source path matters. With it, rsync copies the contents of the folder into the destination. Without it, you get a second example.com folder inside the first.
Cache folders rebuild themselves, so leave them out. Run the command once early to copy the bulk of the files, and again just before the switch. rsync compares both sides and copies only what changed, so the second run finishes much faster.
The -a flag copies a symlink as a link. If a link points outside the WordPress folder, copy its target as well, or add --copy-links so rsync copies the files the link points to.
Without rsync, tar works on any Linux server:
tar -czf ~/site-files.tar.gz -C /var/www/example.com .
Copy the archive across with scp or SFTP, and unpack it on the new server with tar -xzf ~/site-files.tar.gz -C /var/www/example.com.
Step 3: Create the database on the new host
Most control panels have a database screen where you create a database and a user, then give that user all privileges on the database. Write down the database, the user, the password, and the database host the panel shows.
On a server you manage, open the MySQL or MariaDB client as an administrator and run:
CREATE DATABASE wp_example CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_example'@'localhost' IDENTIFIED BY 'use-a-long-random-password';
GRANT ALL PRIVILEGES ON wp_example.* TO 'wp_example'@'localhost';
If the new host already set up a fresh WordPress for you, you can import into its database instead. Check its table prefix in step 5.
Step 4: Import the database
With phpMyAdmin
Select the new database, open the Import tab, choose the .sql.gz file and start the import. The largest file phpMyAdmin accepts is shown next to the file field. For a bigger export, use the command line or ask the host to import it for you.
From the command line
gunzip < ~/site-db.sql.gz | mysql -u wp_example -p wp_example
Once wp-config.php is updated in step 5, WP-CLI can do the same from the WordPress folder with wp db import ~/site-db.sql, after you unzip the file with gunzip ~/site-db.sql.gz.
Two import errors
Unknown collation: ‘utf8mb4_0900_ai_ci’. The old server ran MySQL 8, and the new one runs MariaDB or an older MySQL that does not know this collation. Replace it in the export and import again:
gunzip ~/site-db.sql.gz
sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g' ~/site-db.sql
A text replace on the export is safe here because the collation appears in table definitions, not in your content or saved settings. Step 6 explains why a text replace of your address is not safe.
MySQL server has gone away. One row is larger than the server’s max_allowed_packet setting, often a large saved setting or a post with embedded data. Add --max-allowed-packet=256M to the mysql command, and ask the host to raise the server’s limit if the import still fails.
Step 5: Point wp-config.php at the new database
Open wp-config.php on the new server in a plain text editor, not a word processor, and set the four database values:
define( 'DB_NAME', 'wp_example' );
define( 'DB_USER', 'wp_example' );
define( 'DB_PASSWORD', 'use-a-long-random-password' );
define( 'DB_HOST', 'localhost' );
- DB_HOST is not always localhost. Some hosts give a separate hostname, a port such as
localhost:3307, or a socket path. Use the value from the control panel. - $table_prefix must match the tables you imported. If the old site used
wp_and the host’s fresh install used something likewp_x7k2_, the database now holds both sets of tables, and WordPress reads the set whose prefix wp-config.php gives it. Set the old prefix. - Lines from the old host. Hosts often add their own constants and their own files in wp-content/mu-plugins for caching and server features. Remove any that refer to paths or services the new server does not have.
- Security keys. The AUTH_KEY and SALT lines can stay as they are. Replacing them signs every user out, which you may want if the old server was ever compromised. WordPress.org generates a fresh set on request.
Permissions and ownership
Files that travel through your own computer often arrive with the wrong owner or permissions. On most hosts, directories should be 755, files 644, and wp-config.php 440 or 400 so other accounts on the server cannot read it:
cd /var/www/example.com
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
chmod 440 wp-config.php
If WordPress cannot read wp-config.php after that, PHP runs under a different account from the one that owns the file; use 640 instead. WordPress.org’s guide to file permissions covers the other setups.
The files should belong to the account PHP runs as. On a Debian or Ubuntu server you manage, that is usually chown -R www-data:www-data /var/www/example.com. On shared hosting, your own account owns them already.
Step 6: If the address changes, rewrite it in the database
Skip this step if the site keeps its domain and only moves to a new server. If the domain changes, or the site moves from a subfolder to the root, or from http to https, every copy of the old address in the database has to change.
WordPress keeps its two main addresses in Settings > General: the WordPress Address (siteurl) and the Site Address (home). The old address also appears in post and page content, image links, menus, widgets, theme options and plugin settings.
Why a text editor breaks the site
WordPress and most plugins save settings as PHP serialized data, which records the length of every string. A setting that holds the address looks like this:
a:1:{s:3:"url";s:19:"https://example.com";}
s:19 says the string that follows is 19 characters long. Open the export in a text editor, replace https://example.com with https://www.example.net, and the line becomes:
a:1:{s:3:"url";s:19:"https://www.example.net";}
The string now has 23 characters, but the length still says 19. PHP cannot read the value, so WordPress treats the setting as missing and falls back to defaults: widgets disappear, menus lose their locations, and the theme’s options reset. WordPress shows no error. Only a replacement of exactly the same length survives a text edit.
Rewrite it with WP-CLI
WP-CLI’s search-replace command reads each serialized value, changes the string and recalculates the length. Run it from the WordPress folder on the new server, as a dry run first:
wp search-replace 'https://example.com' 'https://www.example.net' \
--all-tables-with-prefix --skip-columns=guid --dry-run
The dry run lists how many replacements it would make in each table and changes nothing. If the numbers look right, run the same command without --dry-run, then confirm the two main addresses:
wp option get home
wp option get siteurl
--all-tables-with-prefixincludes tables that a plugin created with your prefix but did not register with WordPress. Without it, the command changes only the tables WordPress knows about.--skip-columns=guidleaves each post’s GUID alone. A GUID looks like a link but serves as a permanent ID that feed readers use to tell posts apart, and changing it makes them show old posts as new. WordPress.org’s guide to moving WordPress gives the same advice.--preciseprocesses every column in PHP instead of SQL. It is slower; add it if the dry run reports fewer replacements than you expect.--networkruns across every site of a multisite network.
Some page builders and themes store their data as JSON, where the slashes in an address are escaped. Run a second pass for that form:
wp search-replace 'https:\/\/example.com' 'https:\/\/www.example.net' \
--all-tables-with-prefix --skip-columns=guid
The search-replace reference lists the remaining options.
Without SSH
If the new host gives you no shell access, check its control panel first; many hosts include a web terminal with WP-CLI installed. If not, do the rewrite on your own computer. Import the export into a local copy of the site, run the same command there, and write the result to a new file with --export:
wp search-replace 'https://example.com' 'https://www.example.net' \
--all-tables-with-prefix --skip-columns=guid --export=site-db-new.sql
Import site-db-new.sql on the new host in place of the original export.
If you cannot reach wp-admin
If the site redirects you to the old address before you can run the rewrite, two lines in wp-config.php override the stored addresses:
define( 'WP_HOME', 'https://www.example.net' );
define( 'WP_SITEURL', 'https://www.example.net' );
They change only those two settings. Content and saved settings still hold the old address, so use the lines to get back in, run the rewrite, and then remove them. While they are in place, the two address fields in Settings > General stay locked.
Send the old domain to the new one
Search engines and old links still use the old address. Keep the old domain registered and pointed at a server that answers every request with a permanent redirect. On Apache, put this in the old domain’s .htaccess:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.net/$1 [R=301,L]
Each old URL then redirects to the same path on the new domain. If the site is in Google Search Console, add and verify the new domain there, then use the Change of Address tool from the old domain’s property.
Multisite
A multisite network keeps its domain in more places: the wp_blogs and wp_site tables, the DOMAIN_CURRENT_SITE constant in wp-config.php, and a home and siteurl pair for every site. Run search-replace with --network, then update the domain column in wp_blogs and wp_site by hand. Those columns hold the bare domain without https://, and a replace of the bare domain across the whole database would also change email addresses.
Step 7: Test the new server before you change DNS
Your domain still points at the old server, so the new copy has no public address yet. To see it, tell your own computer to send the domain to the new server by adding a line to its hosts file, with the new server’s IP address:
203.0.113.10 example.com www.example.com
On macOS and Linux the file is /etc/hosts. On Windows it is C:\Windows\System32\drivers\etc\hosts, opened in an editor that runs as administrator. Your computer may keep the old answer cached for a while: ipconfig /flushdns clears it on Windows, sudo killall -HUP mDNSResponder on macOS, and a private browser window helps on both. If the site moved to a new domain that already points at the new server, test on that domain and skip the hosts file.
Unless the new host has issued a certificate already, HTTPS shows a certificate warning during this test. That is fine for your own check, as long as you do not enter real payment details. Some hosts issue the certificate only after DNS points at them; others can issue it early.
Then work through this list:
- You can log in to wp-admin, and Tools > Site Health shows no new critical issues.
- In Settings > Permalinks, click Save Changes without changing anything. On Apache this rewrites .htaccess. Then open a post, a page and a category archive. nginx ignores .htaccess, so inner pages there need the server’s own rule:
try_files $uri $uri/ /index.php?$args; - Images load on a few old posts, and the media library shows thumbnails.
- A contact form sends, and the message arrives.
- On a shop, a test order goes through in the payment provider’s test mode.
- Search works, and so do pages for logged-in users.
Remove the hosts line when you finish, so your computer follows public DNS again.
Step 8: Switch DNS
When the copy works, do the final sync and point the domain at the new server.
- Put the old site into maintenance mode, so no new orders, comments or form entries reach the old database. Create a file named .maintenance in the WordPress folder with the line
<?php $upgrading = time();and WordPress shows its “Briefly unavailable for scheduled maintenance” page to every visitor until you delete the file. - Run the rsync command from step 2 again to copy files that changed since the first copy, mostly new uploads.
- Export the database again, import it on the new server, and repeat step 6 if the domain changes.
- At your DNS provider, change the A record, and the AAAA record if you use IPv6, to the new server’s IP address. Leave the MX and TXT records alone unless your email moves too.
- If the new host could not issue the HTTPS certificate before, issue it now.
If you change nameservers instead of single records, the new DNS provider starts empty. Copy every record across first, especially MX, SPF, DKIM and any verification records, or email stops arriving as soon as the change takes effect.
With a 300-second TTL, most visitors reach the new server within minutes. Some resolvers hold old answers longer, so keep the old server running, still in maintenance mode, for at least a few days. Wait a week before you cancel the old account, and keep the export from the start of the move for longer than that.
After the move
- Clear every cache. That means the page cache, any object cache, and the CDN if you use one. A stale cache can serve pages from the old server or send visitors in a loop between http and https.
- Scheduled tasks. If wp-config.php sets DISABLE_WP_CRON, add the server cron entry on the new host, for example
*/5 * * * * cd /var/www/example.com && wp cron event run --due-now. Without it, scheduled posts, emails and anything else that depends on WordPress’s scheduler stop running. - Email. Send yourself a password reset and a contact form message. If the site sends mail straight from the server, add the new server’s IP address to your domain’s SPF record.
- Mixed content. If the site moved to https, open a few pages with the browser console open and look for images or scripts still loaded over http://. Run the rewrite from step 6 for http://example.com as well.
- PHP extensions. Site Health lists missing modules such as imagick or intl. Ask the host to enable any that your theme or plugins need.
- Search engine visibility. In Settings > Reading, make sure “Discourage search engines from indexing this site” is unticked. Hosts sometimes tick it on new installs and staging copies.
- Backups. The old host’s backups stay with the old host. Set up backups on the new server and run the first one the same day.
When something breaks
| Symptom | Likely cause | Fix |
|---|---|---|
| “Error establishing a database connection” | A wrong value in DB_NAME, DB_USER, DB_PASSWORD or DB_HOST, or a user without privileges on the database | Compare wp-config.php with the control panel and grant the user all privileges on the database |
| The site sends you back to the old domain, or the login page reloads after you log in | home and siteurl still hold the old address, or the browser cached an old redirect | Run the rewrite from step 6, check with wp option get home, and test in a private window |
| A blank page, or “There has been a critical error on this website” | A PHP version or extension mismatch, or a line from the old host in wp-config.php or .htaccess | Set WP_DEBUG and WP_DEBUG_LOG to true in wp-config.php and read wp-content/debug.log |
| The home page works, but every other page returns 404 | .htaccess was not copied, or the server ignores it | Save Settings > Permalinks; on nginx, add the try_files rule from step 7 |
| Images are missing | wp-content/uploads was not copied in full, or image links still use the old domain | Compare the number of files in uploads on both servers, and run the rewrite from step 6 |
| Widgets, menus or theme options are back to defaults | A text replace broke serialized data | Import the original export again and do the rewrite with WP-CLI |
| “Unknown collation: ‘utf8mb4_0900_ai_ci'” during import | A MySQL 8 export imported into MariaDB or an older MySQL | Use the sed command from step 4 |
| Accented letters and emoji show as question marks or stray symbols | The export used a different character set from the tables | Export again with --default-character-set=utf8mb4 |
| “MySQL server has gone away” during import | A row larger than max_allowed_packet | Raise the limit as in step 4 |
| “Unable to create directory” when you upload media or update a plugin | The files belong to the wrong account | Fix the ownership as in step 5 |
Moving a site with Backvera
The manual route works on any host and needs nothing installed. It also means two exports, two imports and an address rewrite that has to be right the first time, while the old site stays in maintenance mode. If you move sites often, a backup that can be restored to a different host does most of these steps for you.
Move your next site with Backvera.
Backvera can clone any backup to a new host or a new domain: to a site already connected to Backvera, to any server you can reach over SFTP, or to a staging copy first. It copies the files and the database, rewrites the old address throughout the database, including inside serialized settings, and gives the copy fresh security keys. You can check the clone before you change DNS, and your original site stays as it is.
Start a 14-day trial with no card and storage included, or read how cloning works.