How To Migrate Drupal 7 to DigitalOcean

This tutorial will detail the exact process of re-installing an existing Drupal 7 site on a new server (droplet) at DigitalOcean. The site is packaged in three pieces: a git repository for the codebase, an archive of the files directory, and a mysql database dump.

WARNING: This is a highly opinionated tutorial based on a real world experience that covers a lot of ground and explains very little. If you would like to know more about why DigitalOcean was chosen for this migration, please read this. Proceed at your own risk, etc..

The Server

Setup an account at DigitalOcean.

Create a new droplet at DO using their one-click LAMP application, which is an Ubuntu 14.04 LTS server. Configure your droplet with at least 1GB of memory. You will get the ip address and root password by email in about 90 seconds. Setup a temporary subdomain to point at this ip, something like migrate.yoursite.com.

Login as root over ssh. DigitalOcean includes an in-browser terminal emulator if you don't have one (I have not tried it). Change the root password when prompted.

Upgrade the server operating system, and install some software.

apt-get update  
apt-get install git drush -y  
apt-get dist-upgrade -y  
reboot  

Create a user, add him to the webserver's group and grant sudo access.

adduser buddyboy  
gpasswd -a buddyboy sudo  
gpasswd -a buddyboy www-data  

Setup a four gigabyte swap file.

fallocate -l 4G /swapfile  
chmod 600 /swapfile  
mkswap /swapfile  
swapon /swapfile  

Edit the filesystems loaded automatically on boot.

nano /etc/fstab  

Add this line to the end:

/swapfile   none    swap    sw    0   0

Server Hardening

Edit the ssh server configuration.

nano /etc/ssh/sshd_config  

Change port from standard, disable root login and add the ClientAliveInterval directive to keep our terminal sessions active.

Port 6969  
PermitRootLogin no  
ClientAliveInterval 60  

Now restart the ssh daemon.

service ssh restart  

Without closing your root shell, test the new user login and permistons.

ssh -p 6969 buddyboy@dropletIPaddress  
sudo ls  

If it works, close your root shell and complete the tutorial logged in as the new user. From this point on, we will assume you've done this and use sudo in all privileged commands.

Setup a basic firewall to block all incoming traffic by default, and allow only the required web and ssh ports:

sudo ufw default deny  
sudo ufw allow 80/tcp  
sudo ufw allow 443/tcp  
sudo ufw allow 6969/tcp  
sudo ufw enable  

Web Server Configuration

We are going to use the LetsEncrypt script to setup our webserver initially, because it does a pretty good job.

First, change ownership of the web root directory and everything in it.

sudo chown -R buddyboy:buddyboy /var/www  

Clone the LetsEncrypt repo and run the script.

sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt  
cd /opt/letsencrypt  
sudo ./letsencrypt-auto --apache -d migrate.yoursite.com  

Enter your email address when prompted and choose to redirect all traffic to ssl.

Edit the newly created apache configuration file.

sudo nano /etc/apache2/sites-enabled/000-default-le-ssl.conf  

Add these lines to get an "A" on the Qualys SSLLabs Test.

SSLEngine on  
SSLCompression off  
SSLProtocol all -SSLv3 -SSLv2  
SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH  

Edit the root user's crontab.

sudo crontab -e  

Add this line to auto-renew your ssl certificates.

0,30 * * * * /opt/letsencrypt/letsencrypt-auto renew > /var/log/le-renew.log  

Change the web root from its default value, add the Directory section and enable .htaccess overrides.

DocumentRoot /var/www/drupal  
<Directory "/var/www/drupal">  
  AllowOverride All
</Directory>  

You will probably want to increase the php memory and execution time

sudo nano /etc/php5/apache2/php.ini  

Find and update these values, if your Drupal 7 site is a pig like mine:

memory_limit = 256M  
max_execution_time = 300  

Don't set the memory limit too high—it should stay as low as possible. If it's too low or too high you will start to see gateway errors.

Drupal can experience some incredibly long execution times on the module and permission pages, so if you don't set the timeout high enough you can end up with partial writes to the database.

Don't restart apache yet.

[ At this point you should consider taking a snapshot of your droplet. It's always nice to have a backup. ]

Drupal Codebase

Clone or extract your codebase into /var/www/drupal

Keep your local configuration in a separate file, so you can easily omit it from the code repository. Add this to your sites/default/settings.php file.

/**
 * Include a local settings file if it exists.
 **/
$local_settings = dirname(__FILE__) . '/settings.local.php';
if (file_exists($local_settings)) {  
  include $local_settings;
}

Create a new directory to hold local resources outside the webroot, and link your local configuration from there into the codebase.

mkdir /var/www/resources  
cd /var/www/resources  
touch settings.local.php  
ln -s /var/www/resources/settings.local.php /var/www/drupal/sites/default/  
vim settings.local.php  

Paste in the following, and update with your password.

<?php  
// Local development configuration.
$databases['default']['default'] = array(
  'database' => 'drupal',
  'username' => 'drupal',
  'password' => 'newpassword',
  'host' => 'localhost',
  'driver' => 'mysql',
  'port' => 3306,
  'prefix' => '',
);
$conf['file_temporary_path'] = '/tmp';
?>

The Files Directory

Extract or move your files directory to /var/www/resources/filesand link it into the codebase. By placing it outside the html root, you can destroy and re-clone the codebase without touching the files directory or its sensitive permissions.

cd /var/www/drupal/sites/default  
ln -s /var/www/resources/files  
chmod 775 -R /var/www/resources/files  
chown www-data:www-data -R /var/www/resources/files  

The web server needs write access to this directory, and we have enabled group permissions so buddyboy can still write to it.

Cron

The "Poor Man's Cron" that ships by default in Drupal 7 is not suitable for a production site. Take the time to configure a proper cron job.

You must get your unique cron url from your Drupal site on the admin/reports page.

Edit a new file:

sudo touch /etc/cron.hourly/drupal_cron  
sudo chmod a+x /etc/cron.hourly/drupal_cron  
sudo nano /etc/cron.hourly/drupal_cron  

Add the following, substituting your own user and URL:

#!/bin/sh
su buddyboy -c '/usr/bin/wget -O - -q https://website.com/cron.php?cron_key=vEZVU63'  
exit 0  

The su command is so that we don't run wget as root.

Database Setup

Create database and user. DigitalOcean puts the root password for MySQL in /etc/motd.tail, which you should change.

mysql -uroot -pYOURPASSWORD  
CREATE DATABASE drupal;  
CREATE USER 'drupal'@'localhost' IDENTIFIED BY 'newpassword';  
GRANT ALL PRIVILEGES ON drupal.* to 'drupal'@'localhost';  
quit  

Import your database:

gunzip < your_database.sql.gz | mysql -u drupal -pYOURPASSWORD drupal  
cd /var/www/drupal  
drush cc all  
reboot  

You should now have a fully functional site. After testing and finishing up any configuration, these are the remaining steps to complete migration with data consistency:

  1. Put the old site in maintenance mode.
  2. Export the database from the old site.
  3. Drop all tables at the new site (drush sql-drop).
  4. Import the dump you just exported into the new database.
  5. Change the DNS to point to the new server.
  6. Re-generate LE certs for the new domain (or use your old ones).
  7. Take the new site out of maintenance mode.

Epilogue–Backup Automation

DigitalOcean will automatically backup each droplet once a week for a small fee. Configure the backup_migrate module to export the database at least once a day. Your codebase will generally be safe because it is replicated at least twice before every deployment, assuming you're using git.

Because the files directory gets written to quite often, we want to keep current backups, however it can get quite large. We will use rsync to copy the directory to a second server where it can be archived without taking server resources from the live site.

Sync this directory to a staging server, an exact duplicate of the live site. Because the database exports from the backup_migrate module are included, this means you can restore a database from the future on your staging server using just the Drupal admin interface. This is very handy for development, and it also means you always have a running server in sync with your live site if disaster strikes. Keep the two servers in separate data centres for maximum redundancy.

sudo touch /etc/cron.hourly/rsync_drupal_files  
sudo chmod a+x /etc/cron.hourly/rsync_drupal_files  
sudo nano /etc/cron.hourly/rsync_drupal_files  

Paste in the following:

#!/bin/sh
# Sync files directory to backup server

cd /var/www/resources/  
/usr/bin/rsync -rP --delete-before -e "ssh -p 6969 -i /home/buddyboy/.ssh/rsync_rsa" syncer@staging_server:/var/www/resources/files_live/ ./files_live/ > /var/log/sync-drupal-files-last.log

exit 0  

You will of course have to setup ssh credentials in order to write the files to the staging server.