In part 11 of Hosting WordPress Yourself I demonstrated how to update a server’s packages (including PHP). However, I didn’t show how to upgrade the server itself, because it’s not something I recommend.
Personally, I don’t ever upgrade a server’s operating system (OS)…
Instead, I prefer to deploy a fresh server and migrate any existing sites.
A much safer approach is to spin up a fresh server. Doing so allows you to test that everything is working as expected fully… Once you’re happy with the new setup, you can switch your DNS over with minimal downtime.
In this article, I’m going to walk you through the steps required to migrate an existing WordPress site to a new server. These are the same steps that I took just a few months ago to migrate the Delicious Brains site to a new server with little to no downtime.
I’m not going to walk you through the process of building a new server. Instead, you should provision a fresh server as outlined in part 1 and part 2 of Hosting WordPress Yourself. We’ve been working hard on a solution for making it super easy to provision new servers tuned for hosting WordPress. (Beta launching soon. Want to be the first to hear about it? Sign up here.)
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:
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.
If you’re unable to connect, go back and verify the previous steps before continuing.
We’ll start by migrating the site’s files, which includes WordPress and any data in the web root. To do so, 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 username@ip_address:/sites/deliciousbrains.com ~/deliciousbrains.com
This will copy the data to our home directory. From there we can move the files into place.
mv ~/deliciousbrains.com /sites/deliciousbrains.com
You can then update the permissions as required, which will vary depending on your server configuration. Usually, they’ll need to be owned by the user running PHP and Nginx.
sudo chown -R www-data:www-data /sites/deliciousbrains.com
With the files taken care of, it’s time to add the site to Nginx.
There are a couple of ways you can add the site to Nginx:
- Create a fresh config based on part 3 of Hosting WordPress Yourself
- Copy the config from the old server
In most cases, I would recommend that you copy 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.
In this example I’m going to copy the existing configuration. As we did with the site data, copy the files using SCP (remember to switch in your credentials and file locations):
scp -r username@ip_address:/etc/nginx/sites-available/deliciousbrains.com ~/deliciousbrains.com
Next, move the files into place and ensure the root user owns them:
sudo mv deliciousbrains.com /etc/nginx/sites-available sudo chown root:root /etc/nginx/sites-available/deliciousbrains.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/deliciousbrains.com /etc/nginx/sites-enabled/deliciousbrains.com
You can test the Nginx configurations for errors using:
sudo nginx -t
The test should pass presuming everything has been copied correctly; however, it will fail if your site is running on HTTPS because we haven’t yet copied the certificate files. Let’s deal with HTTPS now.
Like we did with the site files and Nginx configurations we’re going to copy the certificates using SCP. However, the certificate file permissions are more locked down, so you will need to SSH to the old server and copy them to your home directory.
sudo cp /etc/letsencrypt/live/deliciousbrains.com/fullchain.pem ~/ sudo cp /etc/letsencrypt/live/deliciousbrains.com/privkey.pem ~/
Then, ensure our SSH user has read/write access:
sudo chown username *.pem
Back on the new server, copy the certificates.
scp -r username@ip_address:~/*.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/deliciousbrains.com
You’ll need to update the
ssl_certificate /home/username/fullchain.pem; ssl_certificate_key /home/username/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
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:
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.
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 deliciousbrains_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 deliciousbrains_com.* TO 'username'@'localhost'; FLUSH PRIVILEGES; EXIT;
With that taken care of, it’s time to export the data. As we have access to the server, 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 -u DB_USER -p DB_NAME > ~/export.sql
Switch back to the new server and transfer the database export file:
scp -r username@ip_address:~/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 e-commerce 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:
- Update the live site to show a ‘Back Soon’ message
- Export the live database from the old server
- Import the live database to the new server
- Switch DNS to point to the new server
To stop the live site from modifying the database we’re going to show a ‘Back Soon’ page. The simplest way to do this is to upload an index.html file 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/deliciousbrains.com
Ensure that the
index directive looks like below, which will ensure that our ‘Back Soon’ page is loaded 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. In the case of the Delicious Brains site, this meant that our blog remained up during the migration and only the checkout and account pages were unavailable. It’s worth noting that using this approach will also cause requests to admin-ajax.php and the WordPress REST API to fail. Therefore, you will not be able to use plugins such as WP Migrate DB Pro to perform the migration (unless you do an export).
Before continuing, you should confirm that your live site is indeed showing the ‘Back Soon’ page by 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 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.
On Delicious Brains, we use Cloudflare as our DNS provider, with a TTL of 300 seconds. This meant that most users were routed to the new server quickly. 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.
Now that the new server is live, there are a few loose ends we need to take care of, but I’m not going to detail them here. Instead, I’ll link you to the relevant parts of Hosting WordPress Yourself, which covers them in depth:
- Add a Unix cron
- Ensure automatic backups are running
- Generate a new HTTPS certificate using Let’s Encrypt and configure automatic renewals
Remember, you’ll also need to perform ongoing maintenance as detailed in part 11 to ensure your site remains secure and continues to run optimally.
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.
Have you migrated a site to a new server before? Let us know how it went in the comments below.