Migrate WordPress site with minimal downtime

Migrate_Wordpress_site_with_no_down_timeMigrating a WordPress installation from one server to the other sounds like a simple task. Just move the files, copy the database for WordPress and lets go! But when you start dealing with the DNS entries the whole story gets tricky.

DNS entries take up to 24 hours to be populated to all DNS servers around the world. Even if you have moved the WordPress installation to another server and changed the DNS entries, for up to 24 hours users might still get the IP of the old server. That can of course get your WordPress installation into trouble. As an example, if users can register and those registrations are within those 24 hours, they might register in the old installation and the new one will not know about it. The same if you change the pages or posts, these 24 hours are the time when everything can get troublesome.

Connect to the same database from both servers

One solution is to configure WordPress on the 2 installations to use the same database. Moving the database is something you can control and you can keep as short as possible, but you cannot control the DNS update.

By connecting both installations to the same database, you can ensure that both installations show the same content and you can ensure that registrations to the old installations are also available to the new one.

My setup involved 2 servers in different providers / data-centers. They where even on different continents. Exposing mysql to the public internet is something you don’t want to do. The idea is to protect the connection between those 2 installations. My solution requires access to the shell on each of the servers.

The idea is to create an SSH tunnel from, in this example, the new server to the old server to enable the new installation to connect to the mysql database of the old installation.

I decided to create a dedicated user for this and limit this user to tunnelling only. The following commands will create a shell account with a password and also, for the time of the setup, with a shell. Similar to the Secure SSH server access, I allow login only from a user that is member of a certain group. This group is added as an additional group with the “-G” option.

$ PWD_HASH=`perl -e 'print crypt("secure-passw0rd-for-the-account", "password"),"\n"'`
$ useradd ssh_tunnes_user -p $PWD_HASH -g users -G ssh_login_group -m -s /bin/bash
$ su - ssh_tunnes_user

With the last command, we change to the new user. This will make it easier to setup the user. To be able to reconnect the SSH tunnel in case it fails, I decided to use an SSH key to authenticate. This solves the problem of entering the password every time the tunnel is set up. If you want to know more about this, please see SSH password-less login with SSH key. The following commands will create an ssh key for the new user with a comment. I suggest creating the keys with a comment as this makes it easier to manage them later on. The next two lines will create a line for the authorized_keys file. The options defined here are to prevent the user from getting a shell when logging in. All the details about the possible options can be found at the man page of “sshd(8)“.

$ ssh-keygen -t rsa -b 4096 -C "ssh_tunnes_user@host.example.com"
$ echo -n "no-pty,command=\"/bin/false\" " >/home/ssh_tunnes_user/authorized_keys
$ cat /home/ssh_tunnes_user/.ssh/id_rsa.pub >>/home/ssh_tunnes_user/authorized_keys

Creating the user and the SSH-key needs to be done on both servers. As soon as this is done copy the authorized_keys file from the old server to the new server and vice versa. The easiest way to do so is by using scp:

$ scp -P1234 /home/ssh_tunnes_user/authorized_keys ssh_tunnes_user@hostname.example.com:/home/ssh_tunnes_user/.ssh/

If you have a non-standard ssh port you want to use, the “-P” option to define the port. (Tricky here is that ssh uses a lower case “-p” option whereas scp uses an upper case “-P” parameter for the port).

As soon as this is in place, the ssh tunnel for mysql can be established. Instead of just firing up a tunnel manually, I suggest to create a script to enable you to keep track of the reconnecting attempts as well. Here is a short minimal example of how that could look. I save this script as ssh_tunnel_script.sh:

#!/bin/bash

# write initial log message
echo "Connecting to server ... "

while true
do
  ssh -N -p 1234 ssh_tunnes_user@host.example.com -L 33066:127.0.0.1:3306
  sleep 10
  echotime "Reconnecting to server ..."
done

What does this script do? This script will echo a log message and then start the ssh tunnel without entering a shell (-N parameter) since the user has no shell access and so the connection would fail if this parameter was not set. If the connection gets disconnected for some reason, the script will wait 10 seconds, write a line as output and start connecting again. This is done in an endless loop. To end this script, you need to terminate it manually via kill(1).

As an alternative, you could also use the SSH-Tunnel-Manager bash script I have created. The SSH-Tunnel-Manager will handle all the complicated stuff for you. You can start/stop/restart the configured tunnels and use it with init scripts. It comes with a configuration file where you can configure all the forwarding ports you need.

$ ssh_tunnel_script.sh >>/path/to/script_logfile.log &

This will redirect the output from the script into a logfile where you can check how often the tunnel was reconnected. Letting this tunnel run for some time and checking the log might be a good idea to see if the connection is stable before continuing.

Dealing with SELinux

In my setup, SELinux was protecting my CentOS installation and must be configured to allow the webserver to connect to the port configured for the tunnel (33066). SELinux provides a number of predefined rules that can be enabled by the so-called SELinux boolean switches. The predefined rules for our needs can be enabled via the “httpd_can_network_connect” switch. To check the value of the boolean switches run “getsebool -a” or since CentOS 6 “semanage boolean -l” is available as well:

$ setsebool -P httpd_can_network_connect=on

Configure WordPress

Now you can configure the new WordPress installation to connect to the database of the old server via “127.0.0.1:33066” as database hostname.

This will allow you to change the site’s DNS entry without being afraid of losing any registrations. As soon as the DNS change is propagated to all DNS worldwide (usually after 24 hours), you can move the database over from the old server to the new one (which is not in the scope of this article). It is just a matter of dumping mysql on the old server and restoring it on the new one, and of course changing the WordPress configuration to connect to the new server.

warningDuring the period of moving the wordpress installation, both installations are active like an active-active cluster. If you use any caching plugins, make sure you disable them as they can lead to unexpected behaviour. These plugins tend to store a cached version of the pages/posts in the file-system which is not replicated between the two servers.

 

WordPress uploads?

With the technique used above, the database information is the same for both installations, but for the whole installation to stay in sync, the files on the webservers need to be synced as well. Not syncing them could lead to uploaded content only being available on one of the servers.


Read more of my posts on my blog at http://blog.tinned-software.net/.

This entry was posted in DNS, Security and tagged , , , . Bookmark the permalink.