In the previous chapter we enhanced security and performance with tweaks to the Nginx configuration. In this article, I’m going to walk you through the steps required to migrate an existing WordPress site to a new server.

There can be lots of reasons to migrate a site. Perhaps you’re moving from one web host to another. If you’re moving a site to a server you’ve set up with SpinupWP, the following guide will work but I recommend using our documentation on migrating a site to a SpinupWP server for more specific instructions. I promise it will save you time and headaches. 🙂

Another good reason to migrate a site is to retire a server. We do not recommend upgrading a server’s operating system (OS). That is, we don’t recommend upgrading Ubuntu even though Ubuntu might encourage it. The truth is a lot can go wrong upgrading the OS of a live server and it’s just not worth the trouble.

A much safer approach is to spin up a fresh server, migrate existing sites, and shut down the old server. This approach allows you to test everything is working on the new server before switching the DNS and directing traffic to it.

If you haven’t already completed the previous chapters to fire up a fresh new server, you should start at the beginning. (Interested in a super quick and easy way to provision new servers tuned for hosting WordPress? Check out how SpinupWP works.) Let’s get started!

Securely Copying Files

Before we begin migrating files, we need to figure out the best way to copy them to the new server. There are a couple of methods, including SFTP, but the safest and quickest route is to use SCP.

SCP will allow us to copy the files server-to-server, without first downloading them to our local machine. Under the hood, SCP uses SSH; therefore we need to generate a new SSH key so that we can connect to our old server from the new server. On the newly provisioned server, create a new SSH key using the following command:

ssh-keygen -t rsa -b 4096 -C "your_server_ip_or_hostname"

Then copy the public key to your clipboard. You can view the public key, like so:

cat ~/.ssh/id_rsa.pub

On the old server add the public key to your authorized_keys file:

sudo echo "public_key" >> ~/.ssh/authorized_keys

Then verify that you’re able to connect to the old server from the new server using SSH.

ssh ashley@pluto.ashleyrich.com

If you’re unable to connect, go back and verify the previous steps before continuing.

File Migration

We’ll start by migrating the site’s files, which includes WordPress and any files in the web root. Issue the following command from the new server. Remember to substitute your old server’s IP address and the path to the site’s web root.

scp -r ashley@pluto.ashleyrich.com:~/ashleyrich.com ~/ashleyrich.com

With the files taken care of, it’s time to add the site to Nginx.

Nginx Configuration

There are a couple of ways you can add the site to Nginx:

  1. Create a fresh config based on chapter 3
  2. Copy the config from the old server

I recommend copying the existing configuration, as you know it works. However, starting afresh can be useful, especially if your virtual host file contains a lot of redundant directives. You can download a zip of complete Nginx configs as a fresh starting point.

In this example I’m going to copy the existing configuration. As we did with the site data, copy the file using SCP:

scp -r ashley@pluto.ashleyrich.com:/etc/nginx/sites-available/ashleyrich.com ~/ashleyrich.com

Next, move the file into place and ensure the root user owns it:

sudo mv ashleyrich.com /etc/nginx/sites-available
sudo chown root:root /etc/nginx/sites-available/ashleyrich.com

The last step is to enable the site in Nginx by symlinking the virtual host into the enabled-sites directory:

sudo ln -s /etc/nginx/sites-available/ashleyrich.com /etc/nginx/sites-enabled/ashleyrich.com

Before testing if our configuration is good, we should copy over our SSL certificates.

SSL Certificates

Certificate file permissions are more locked down, so you will need to SSH to the old server and copy them to your home directory first.

sudo cp /etc/letsencrypt/live/ashleyrich.com/fullchain.pem ~/
sudo cp /etc/letsencrypt/live/ashleyrich.com/privkey.pem ~/

Then, ensure our SSH user has read/write access:

sudo chown ashley *.pem

Back on the new server, copy the certificates.

scp -r ashley@pluto.ashleyrich.com:~/*.pem ~/

We’re going to generate fresh certificates using Let’s Encrypt once the DNS has switched over (see Finishing Up), so we’ll leave the certificate files in our home directory for the time being and update the Nginx configuration to reflect the new paths.

sudo nano /etc/nginx/sites-available/ashleyrich.com

You’ll need to update the ssl_certificate and ssl_certificate_key directives.

ssl_certificate /home/ashley/fullchain.pem;
ssl_certificate_key /home/ashley/privkey.pem;

To confirm the directives are correct, once again test the Nginx config:

sudo nginx -t

If everything looks good, reload Nginx:

sudo service nginx reload

Spoof DNS

It’s a good idea to test the new server as we go. We can do this by spoofing our local DNS, which will ensure the old server remains active for your visitors but allow you to test the new server. On your local machine add an entry to your /etc/hosts file, which points the new server’s IP address to the site’s domain:

46.101.3.65    ashleyrich.com

Once updated, if you refresh the site you should see “Error establishing a database connection” because we haven’t imported the database yet. Let’s handle that next.

Before continuing, remember that the domain now points to the new server’s IP address. If you usually SSH to the server using the hostname, this will no longer work. Instead, you should SSH to each server using their IP addresses until the migration is complete.

Database Import

Before we can perform the import, we need to create the database and database user. On the new server, log in to MySQL using the root user:

mysql -u root -p

Create the database:

CREATE DATABASE ashleyrich_com CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;

Then, create the database user with privileges for the new database:

CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON ashleyrich_com.* TO 'username'@'localhost';
FLUSH PRIVILEGES;
EXIT;

With that taken care of, it’s time to export the data. We’re going to use mysqldump to perform the database export. If you need to do anything more complex, like exclude post types or perform a find and replace on the data, I would recommend using WP Migrate DB Pro.

To export the database, issue the following command from the old server, replacing the database credentials with those found in your wp-config.php file:

mysqldump --no-tablespaces -u DB_USER -p DB_NAME > ~/export.sql

Switch back to the new server and transfer the database export file:

scp -r ashley@pluto.ashleyrich.com:~/export.sql ~/

Finally, import the database:

mysql -u DB_USER -p DB_NAME < export.sql

If any of the database connection information is different from that of the old server you will need to update your wp-config.php file to reflect those changes. Refresh the site to confirm that the database credentials are correct. If everything is working, you should now see the site.

It’s Time to Test

You now have an exact clone of the live site running on the new server. It’s time to test that everything is working as expected.

For ecommerce sites, you should confirm that the checkout process is working and any other critical paths. Remember, this is only a clone of the live site, so anything saved to the database won’t persist, as we’ll be re-importing the data shortly.

Once you’re happy that everything is working as expected, it’s time to perform the migration.

Migrating with Minimum Downtime

On busy sites, it’s likely that the database will have changed since performing the previous export. For us to ensure data integrity, we need to prevent the live site from modifying the database while we carry out the migration. To do that we’ll perform the following actions:

  1. Update the live site to show a ‘Back Soon’ message
  2. Export the live database from the old server
  3. Import the live database to the new server
  4. Switch DNS to point to the new server

To stop the live site from modifying the database we’re going to show the following ‘Back Soon’ page:

<!doctype html>
<html>
    <head>
        <title>Back Soon</title>
        <style>
          body { text-align: center; padding: 150px; }
          h1 { font-size: 50px; }
          body { background-color: #e13067; font: 20px Helvetica, sans-serif; color: #fff; line-height: 1.5 }
          article { display: block; width: 650px; margin: 0 auto; }
        </style>
    </head>

    <body>
        <article>
            <h1>Back Soon!</h1>
            <p>
                We're currently performing server maintenance.<br>
                We'll be back soon!
            </p>
        </article>
    </body>
</html>

We’ll save this as an index.html page, upload it to the web root and update Nginx to serve this file, instead of index.php.

On the old server, modify your site’s virtual host file:

sudo nano /etc/nginx/sites-available/ashleyrich.com

Ensure that the index directive looks like below, which will ensure that our ‘Back Soon’ page is loaded for all requests instead of WordPress:

index index.html index.php;

Once done, reload Nginx. Your live site will now be down. If you’re using Nginx FastCGI caching, any cached pages will continue to be served from the cache. However, requests to admin-ajax.php and the WordPress REST API will fail. Therefore, you will not be able to use plugins such as WP Migrate DB Pro to perform the migration.

Before continuing, you should confirm that your live site is indeed showing the ‘Back Soon’ page by checking it from another device or removing the entry from your /etc/hosts file, which we added earlier.

Flipping the Switch

Now that the live site is down it’s time to export and import the database once more (as we did above) so that any changes that occurred to the database while we were testing are migrated. However, this time you won’t need to create a database or database user.

Once the export/import is complete you may want to add the entry back into your /etc/hosts file (if you removed it) so that you can quickly check that the database migration was successful. Once you’re confident that everything is working as expected, log into your DNS control panel and update your A records to point to the new server. Modifying your DNS records will start routing traffic to your new server. However, keep in mind that DNS queries are cached, so anyone who has visited your site recently will likely still be routed to the old server and see the ‘Back Soon’ page. Once the user’s machine re-queries for the domain’s DNS entries they should be forwarded to the new server.

We use Cloudflare as our DNS provider, with a TTL of 300 seconds. This means that most users are routed to the new server quickly when we make a DNS change. However, if your DNS TTL is higher, I would recommend lowering it a few days prior to performing the migration. This will ensure DNS changes propagate more quickly to your users.

Finishing Up

Now that the new server is live, there are a few loose ends we need to take care of, but fortunately we’ve already covered them in previous chapters:

  1. Add a Unix cron
  2. Ensure automatic backups are running
  3. Generate a new SSL certificate using Let’s Encrypt

That’s everything there is to know about migrating a WordPress site to a new server. If you follow the steps outlined here, you should have a smooth migration with little downtime. In the final chapter we’ll cover how to keep your server and sites operational with ongoing maintenance and monitoring.