WordPress sites get hacked all the time, because the typical WordPress blogger install 100’s of shitty plugins and rarely updates their site. On the one hand, it’s great that WordPress has empowered so many people to begin blogging without requiring the ‘hard’ technical skills, on the other it just gives criminals a large number of potential victims.

Two years ago, when I studied the details of phishing attacks that targeted Maybank and RHB, I found that attackers use compromised WordPress sites to host their phishing content. They’d first hack into a seemingly random WordPress website, host their phishing content there, and then blast out emails to unsuspecting victims with links to pointing back to their hacked bounty. If the hack works they’d get free username and passwords, and if they were ever caught, most evidence would point to the unsuspecting Wordpress site owner.

So if you have a WordPress site (like me), chances are you’re in the cross-hairs of hackers already, and securing your site is the responsible thing to do.

In general Wordpress sites should be:

  • Updated Automatically
  • Use a minimal number of plugins
  • Use plugins only from reputable publishers
  • Use themes only from reputable publishers--and have only one theme in the install directory
  • Employ strong passwords for the admin & user
  • Have the permissions of the underlying folders set accordingly (i.e.CHMOD them all)
But even if you took all precautions to hardened your site, there's always a possibility of it getting hacked. No security is perfect, and you should look into backups--backup often and to a separate location. That way, a compromised site can be rebuilt, even if it were defaced. The last thing you want is to lose your precious design and data, because some one installed a shitty plugin over the weekend.

Today, I’ll walk through a short bash script I wrote to backup (and restore) a WordPress installation from scratch. It took me quite a while to write this (partly because I have no experience with Bash scripts), but I thought it would be good to walkthrough the details of the script and what it does.

The full script is available on github here, and the usage instructions will be maintained there. The write-up below describes code the first production release, linked here, even though I’ve since updated the scripts to include some modifications, and as we speak I’m just about the release version 1.2.

So here we go…

Special Thanks

The following 3 folks, were greatly influential in the writing of the script, listed in no particular order. No to mention, the wonderful folks at stackoverflow that helped tremendously as well.

Thanks to Andrea Fabrizi for the awesome DropboxUploader script Thanks to Ben Kulbertis for the awesome Cloudflare update script Thanks to Peteris.Rocks for inspiring me with his Unattended WordPress Installation script

Pre-Requisites

As a pre-requisite to all this, I made the following decisions.

The back ups would be stored in DropBox– Dropbox has free options (up to 2GB) and has versioning by default.All your backups are versioned and kept for 30 days (not just the latest upload, which gets destroyed if you’re hit by malware). Doing this on AWS requires extra work, which I wasn’t prepared to do, and AWS has no free tier for S3 storage.

Also, I use CloudFlare to maintain the DNS. It’s optional of course, but I needed a DNS provider that had an API, and they were the logical choice. This allowed the script to update your DNS as well.

Finally, the script assumes a standard LAMP stack, i.e. Linux (specifically Ubuntu 16.04), Apache , MySql and PHP. PHP is enforced by Wordpress itself so that’s fine.But the ’trend’ these days is to have NGINX instead of Apache, and MariaDB instead of MySQL. I kept things in ‘classic’ mode for now, I may revisit in the future.

High level setup

The entire thing (both backup and restore) runs on Bash scripts. There are 5 .sh files, 3 of which are core :
  • setup.sh - first time initialization of the scripts (use this for a site that already has Wordpress installed)
  • backupWP.sh - backs up the Wordpress Installation (called from a cronjob)
  • restoreWP.sh - restores Wordpress Installation on a blank machine
The remaining 2 non-core files:
  • cloudflare.sh - updates Cloudflare DNS entry
  • functions.sh - common functions between 3 core files
Non-core files are only called from the core scripts, and are used to keep common functions in a single re-usable location.

File 1: Setup.sh

Setup.sh takes in 4 command line arguments as defined in the README.md file. I won't repeat here. Once all the usual command-line parameter parsing is done, we get into the meat of things.

Section 1: Download DropboxUploader

DropboxUploader is a fantastic piece of code that enables upload/download to Dropbox via command line. The first line of my code downloads it and sets it up according.
#---------------------------------------------------------------------------------------
# Download DropboxUploader and Setup
#---------------------------------------------------------------------------------------
GetDropboxUploader $DROPBOXTOKEN #in functions.sh

Section 2: Setup wpsettings File

The .wpsettings file is an important configuration file created by the script to store values for future use. Since the back up script should run everyday without user intervention, we need to store the configuration parameters for this setup somewhere on disk, and I used a file called .wpsettings stored in the user folder.

To maintain idempotency, the script first checks for any old .wpsettings file and deletes them before starting. You’ll see this often throughout the script to ensure that I can run the script over and over again without any worries.

Once done, I create a .wpsettings file from scratch that stores:

  • WPDIR : The WordPress installation directory
  • WPCONFDIR: The Wordpress configuration Directory (the wp-config.php file can be stored somewhere else)
  • DROPBOXPATH: The Path for the DropBoxUploader.
The script also creates a an .enckey file, that stores the encryption key. All uploads to dropbox are encrypted before transmission (for added security), and obviously the encrypted key shouldn't be uploaded to Dropbox. This way, even if your Dropbox is compromised, the backup wordpress files have a separate layer of protection afforded by AES-256 encryption.

When you wish to restore you’ll need the provide the encryption key to the restoration script.I created the full call (including the encryption key as a command-line argument) and stored it in Lastpass under my encrypted notes. That way, I can simply spin up an instance of Ubuntu on any cloud provider, and run a few lines of code I copied from Lastpass to restore my site.

#---------------------------------------------------------------------------------------
# Setup .wpsettings file
#---------------------------------------------------------------------------------------
if [ -f "$WPSETTINGSFILE" ]; then
echo "Deleting old $WPSETTINGSFILE (probably from previous installation)"
rm $WPSETTINGSFILE
fi

echo “WPDIR=$WPDIR” >> $WPSETTINGSFILE #store wordpress directory in config file echo “WPCONFDIR=$WPCONFDIR” >> $WPSETTINGSFILE #store wordpress config (wp-config.php) directory in config file echo “DROPBOXPATH=$DROPBOXPATH” >> $WPSETTINGSFILE #store dropbox uploader path in directory

SetEncKey $ENCKEY #in functions.sh

Section 3: Setup CRON job

At the end, a Cronjob is created to ensure backupWP.sh runs are a specified time everyday. For more info on these functions look into functions.sh for SetCronJob
#---------------------------------------------------------------------------------------
# Download Backup Script and create CRON job
#---------------------------------------------------------------------------------------

SetCronJob #in functions.sh

echo “Setup Complete”

File 2: backupWP.sh

backupWP.sh doesn't take in any command line arguments (how could it? it runs automatically everyday)

Section 1: Delete previous backups

To ensure everything is idempotent, again we have check if the backup directory exist. If it does, we delete it and create a new one, ensuring we have a nice and empty backup folder to store our new backups in.
#---------------------------------------------------------------------------------------
# Download Backup Script and create CRON job
#---------------------------------------------------------------------------------------

SetCronJob #in functions.sh

echo “Setup Complete”

Section 2: Backup DB

The following section grabs the data from wp-config.php to access the database. And then makes a .sql backup of the database. Reading the database parameters from the wp-config.php file ensures that I don't have to store the database credentials anywhere else.
#-------------------------------------------------------------------------
# mysqldump the MYSQL Database
#-------------------------------------------------------------------------
echo -e "\\n######### Backing Up Mysql Database BEGIN #########\\n"

WPDBNAME=</span>cat <span style="color: #19177c;">$WPCONFDIR</span>/wp-config.php | grep DB_NAME | cut -d <span style="color: #bb6622; font-weight: bold;">\'</span> -f 4<span style="color: #ba2121;"> WPDBUSER=</span>cat <span style="color: #19177c;">$WPCONFDIR</span>/wp-config.php | grep DB_USER | cut -d <span style="color: #bb6622; font-weight: bold;">\'</span> -f 4<span style="color: #ba2121;"> WPDBPASS=</span>cat <span style="color: #19177c;">$WPCONFDIR</span>/wp-config.php | grep DB_PASSWORD | cut -d <span style="color: #bb6622; font-weight: bold;">\'</span> -f 4<span style="color: #ba2121;">

if [ -z $WPDBNAME ]; then echo “ERROR: unable to extract DB NAME from $WPCONFDIR/wp-config.php” exit 0 else echo “INFO: Dumping MYSQL Files” mysqldump -u $WPDBUSER -p$WPDBPASS $WPDBNAME | sudo tee $BACKUPPATH/$WPSQLFILE > /dev/null echo “GOOD: MYSQL successfully backed up to $BACKUPPATH/$WPSQLFILE” fi

echo -e "\n######### END #########\n"

Section 3: Zip WordPress & Apache Directory

A separate assumption is that Wordpress is installed with Apache, and this part of the script creates two zip files, one for the WordPress installation, and another for the Apache configuration (typically /etc/apache2).

This allows us to copy over SSL certs and apache configuration to restore, on the fly. Later on the script, we grab data from letsencrypt (if it exist).

#-------------------------------------------------------------------------
# Zip $WPDIR folder
#-------------------------------------------------------------------------
echo -e "\\n######### Zipping Wordpress BEGIN #########\\n"

echo “INFO: Zipping the $WPDIR to : $BACKUPPATH/$WPZIPFILE” sudo tar -czf $BACKUPPATH/$WPZIPFILE -C $WPDIR . #turn off verbose and don’t keep directory structure echo “INFO: $WPDIR successfully zipped to $BACKUPPATH/$WPZIPFILE”

echo -e "\n######### END #########\n" #————————————————————————- # Copy all Apache Configurations files #————————————————————————- echo -e "\n######### Zipping APACHE BEGIN #########\n"

echo “INFO: Zipping $APACHEDIR” sudo tar -czf $BACKUPPATH/$APACHECONFIG -C $APACHEDIR . #turn off verbose and don’t keep directory structure echo “INFO: $APACHEDIR successfully zipped to $BACKUPPATH/$WPZIPFILE”

echo -e "\n######### END #########\n"

Section 4: Encrypt backup files and remove unencrypted ones

Before uploading to Dropbox, we need to encrypt the files (for security purposes) and delete the unencrypted ones, so that they're not lying around.
#-------------------------------------------------------------------------
# Encrypting files before uploading
#-------------------------------------------------------------------------
echo -e "\\n######### Encrypting files BEGIN #########\\n"

echo -e “INFO: Encrypting MYSQL FIles” sudo openssl enc -aes-256-cbc -in $BACKUPPATH/$WPSQLFILE -out $BACKUPPATH/$WPSQLFILE.enc -k $ENCKEY sudo rm $BACKUPPATH/$WPSQLFILE #remove unencrypted file

echo -e “INFO: Encrypting Wordpress Backup file:" sudo openssl enc -aes-256-cbc -in $BACKUPPATH/$WPZIPFILE -out $BACKUPPATH/$WPZIPFILE.enc -k $ENCKEY sudo rm $BACKUPPATH/$WPZIPFILE #remove unencrypted file

echo -e “INFO: Encrypting Apache Configuration” sudo openssl enc -aes-256-cbc -in $BACKUPPATH/$APACHECONFIG -out $BACKUPPATH/$APACHECONFIG.enc -k $ENCKEY sudo rm $BACKUPPATH/$APACHECONFIG #remove unencrypted file

# Encrypt wp-config.php file if [ "$WPCONFDIR” != "$WPDIR" ]; then #already copied, don’t proceed echo “INFO: Encrypting wp-config.php file in $WPCONFDIR”
sudo openssl enc -aes-256-cbc -in $WPCONFDIR/$WPCONFIGFILE -out $BACKUPPATH/$WPCONFIGFILE.enc -k $ENCKEY else echo “INFO: wp-config.php file is in the wordpress directory, no separate zipping necessary” fi

sudo openssl enc -aes-256-cbc -in $WPSETTINGSFILE -out $BACKUPPATH/$WPSETTINGSFILENAME.enc -k $ENCKEY echo -e “WARNING: The encryption key in $ENCKEYFILE will not be uploaded to Dropbox” echo -e “WARNING: Store $ENCKEYFILE in a safe place”

echo -e "\n######### END #########\n"

Section 5: Upload to Dropbox

Finally we use dropbox_uploader.sh (which was downloaded during setup.sh), to upload the files into Dropbox.
#-------------------------------------------------------------------------
# Upload to Dropbox
#-------------------------------------------------------------------------
echo -e "\\n######### Upload to Dropbox BEGIN #########\\n"

echo -e “INFO: Uploading Files to Dropbox” sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$WPSQLFILE.enc / sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$WPZIPFILE.enc / sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$APACHECONFIG.enc / if [ "$WPCONFDIR" != "$WPDIR" ]; then #already copied, don’t proceed sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$WPCONFIGFILE.enc / fi sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$WPSETTINGSFILENAME.enc /

echo -e "\n######### END #########\n"

Section 6: Letsencrypt

As you can tell, this was a last-minute bolt-on. If you used Letsencrypt to generate your certificates, this part of the script copies across the letsencryt configuration and files to be restored later on.

You might be wondering–why back this up at all? Why not just run letsencrypt during the restoration process, and you’d be right. But as I said, this was a last-minute bolt-on.

Letsencrypt only works if your domain already resolves to the new server. During restoration, you might find yourself waiting 24 hours before the new DNS entry propagates across the internet.–hence copying over letsencrypt gives you the opportunity you set server even before the resolution has been fixed.

#-------------------------------------------------------------------------
# Lets Encrypt
#-------------------------------------------------------------------------
echo -e "\\n######### LetsEncrypt BEGIN #########\\n"
if [ -d $LETSENCRYPTDIR ]; then
    echo -e "INFO: LetsEncrypt detected, backing up files"
    sudo tar -czf $BACKUPPATH/$LETSENCRYPTCONFIG -C $LETSENCRYPTDIR .
    echo -e "INFO: Encrypting Letsencrypt Configuration"
    sudo openssl enc -aes-256-cbc -in $BACKUPPATH/$LETSENCRYPTCONFIG -out $BACKUPPATH/$LETSENCRYPTCONFIG.enc -k $ENCKEY
    sudo rm $BACKUPPATH/$LETSENCRYPTCONFIG #remove unencrypted file
    echo -e "INFO: Uploading Letsencrypt to Dropbox"
    sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$LETSENCRYPTCONFIG.enc /
else
    echo -e "LetsEncrypt not found"
fi
echo -e "\\n#########    END    #########\\n"

echo -e "\n######### SCRIPT END #########\n"

File 3: restoreWP.sh

Easily the most complex of the 3 core files is restoreWP.sh. This script is meant to run on a bare Ubuntu 16.04 box, and will build the WordPress Installation from scratch, specifically:
  • Update Cloudflare DNS entry to point to the new server (optional)
  • Download the backup files from Dropbox
  • Restore Wordpress files (preserving directory & configuration..passwords etc)
  • Download necessary packages for MySQL
  • Restore Wordpress Database (preserving username, password, db name..etc)
  • Download all necessary packages Apache & PHP
  • Restore Apache configuration (optional) or; Build Apache configuration from scratch
  • Setup backup script (to continue backing up to cloud)
  • Setup a swapfile configuration (1GB)
  • Setup firewall rules for Ubuntu (ports 22,80,443 only)
  • Restore letsencrypt configuration from backup or; Call letsencrypt to get certificates or
Everything here has an order for a reason.
  • Cloudflare 'should' got first, because DNS propagation takes time. Placing it first is just logical.
  • Download the files from Dropbox before installing packages, because if downloads aren't available, end script now
  • We restore wordpress first, because we need the DB parameters from wp-config file to setup DB
  • Setup DB according to user,database name and password set in previous WP configuration
  • Finally we restore Apache last, because it's the most complicated
  • Once there, we setup backup, swapfile and firewall
  • Only then call letsencrypt because letsencrypt is the only interactive portion
    • I chose to use standard letsencrypt call which is interactive (Certbot)
    • but it means I'm not maintaining a custom script for this...more sustainable

Section 0: Main Initialization

#---------------------------------------------------------------------------------------
# Main-Initilization
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### REPO UPDATE #########\\n\\n"

echo “INFO: Updating REPO” sudo apt-get update >>$LOGFILE #we will upgrade after deletion of unwanted packages export DEBIAN_FRONTEND=noninteractive #Silence all interactions

Section 1: Update Cloudflare DNS entry

A quick update on Cloudflare, I placed it at the beginning simply because DNS updates take time to propogate, so putting it upfront seemed to make sense.

What makes less sense though, is that if the script fails later on, the DNS has already been updated. That’s a risk worth taking. If you really are worried don’t supply the cloudflare credentials and the DNS update won’t occur.

#---------------------------------------------------------------------------------------
# DNS Update with Cloudflare - (done first because it takes time to propagate)
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### CLOUDFLARE UPDATE #########\\n\\n"

if [ "$DNSUPDATE" = true ]; then

<span style="color: #008000;">echo</span> <span style="color: #ba2121;">"INFO: Updating cloudflare record $CFRECORD in zone $CFZONE using credentials $CFEMAIL , $CFKEY "</span>
./cloudflare.sh --email <span style="color: #19177c;">$CFEMAIL</span> --key <span style="color: #19177c;">$CFKEY</span> --zone <span style="color: #19177c;">$CFZONE</span> --record <span style="color: #19177c;">$CFRECORD</span>
<span style="color: #008000;">echo</span> <span style="color: #ba2121;">"INFO: Removing Cloudflare script"</span>
rm cloudflare.sh <span style="color: #408080; font-style: italic;">#you only need it once</span>
<span style="color: #008000;">echo</span> <span style="color: #ba2121;">"GOOD: Cloudflare update complete"</span>

else echo “WARNING: DNS wasn’t updated” fi

echo -e "\n\n######### CLOUDFLARE UPDATE END#########"

Section 2: Remove Previous Installations (Idempotent)

Just to make sure the server is a blank Ubuntu, we uninstall all the stuff we'll install later on.
#---------------------------------------------------------------------------------------
# Remove previous installations if necessary
#---------------------------------------------------------------------------------------
echo "INFO: Attempting to delete older packages if they exist -- idempotency"
sudo apt-get --purge -y remove mysql* >>$LOGFILE #remove all mysql packages
sudo apt-get --purge -y remove apache2 >>$LOGFILE 
sudo apt-get --purge -y remove php >>$LOGFILE
sudo apt-get --purge -y remove libapache2-mod-php >>$LOGFILE
sudo apt-get --purge -y remove php-mcrypt >>$LOGFILE
sudo apt-get --purge -y remove php-mysql >>$LOGFILE
sudo apt-get --purge -y remove python-letsencrypt-apache >>$LOGFILE

sudo apt-get -y autoremove >>$LOGFILE sudo apt-get -y autoclean >>$LOGFILE

echo “INFO: Upgrading installed packages” #do this after deletion to avoid upgrading packages set for deletion #sudo apt-get upgrade >>$LOGFILE #Disabled for now

echo -e "\n\n######### REPO UPDATE COMPLETE #########\n\n"

Section 3: Download files from Dropbox

Setup DropboxUploader, and then download the backup files from Dropbox.

$WPSETTINGSFILE is downloaded first, because it contains the basic wordpress configuration information, which will be useful later on. You’ll notice I use openssl to decrypt the files, because by default all files are encrypted to before being uploaded to Dropbox as part of the backup script.

#---------------------------------------------------------------------------------------
#Setup DropboxUploader
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### Downloading from Dropbox #########\\n\\n"

GetDropboxUploader $DROPBOXTOKEN #in functions.sh

#————————————————————————————— #Download .wpsettings file #————————————————————————————— delFile $WPSETTINGSFILE delFile $WPSETTINGSFILEDIR/$WPSETTINGSFILE #remove old wpsettings file (if exists)–functions.sh

echo “INFO: Checking if $WPSETTINGSFILE exist on Dropbox” sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$WPSETTINGSFILE.enc #wpsettings file

if [ -f $WPSETTINGSFILE.enc ]; then echo “GOOD: $WPSETTINGSFILE exist, decrypting and loading” sudo openssl enc -aes-256-cbc -d -in $WPSETTINGSFILE.enc -out $WPSETTINGSFILEDIR/$WPSETTINGSFILE -k $ENCKEY echo “INFO: Loading $WPSETTINGSFILE” source "$WPSETTINGSFILEDIR/$WPSETTINGSFILE" 2>/dev/null #file exist, load variables else echo “ERROR: unable to find $WPSETTINGSFILE, check dropbox location to see if the file exists” exit 0 fi

#————————————————————————————— #Download files from dropbox #————————————————————————————— delFile $WPSQLFILE #delete files if it exist, functions.sh delFile $WPZIPFILE delFile $APACHECONFIG delFile $LETSENCRYPTCONFIG delFile $WPCONFIGFILE

echo “INFO: Downloading and decrypting SQL backup file” sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$WPSQLFILE.enc #Wordpress.sql file sudo openssl enc -aes-256-cbc -d -in $WPSQLFILE.enc -out $WPSQLFILE -k $ENCKEY

echo “INFO: Downloading and decrypting Wordpress zip file” sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$WPZIPFILE.enc #zip file with all wordpress contents sudo openssl enc -aes-256-cbc -d -in $WPZIPFILE.enc -out $WPZIPFILE -k $ENCKEY

echo “INFO: Downloading and decrypting Apache configuration” sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$APACHECONFIG.enc #zip file with all wordpress contents sudo openssl enc -aes-256-cbc -d -in $APACHECONFIG.enc -out $APACHECONFIG -k $ENCKEY

echo “INFO: Downloading and decrypting LetsEncrypt configuration” sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$LETSENCRYPTCONFIG.enc #zip file with all wordpress contents if [ -f $LETSENCRYPTCONFIG.enc ]; then sudo openssl enc -aes-256-cbc -d -in $LETSENCRYPTCONFIG.enc -out $LETSENCRYPTCONFIG -k $ENCKEY else echo “WARNING: Letsencrypt.tar not found” fi

if [ "$WPDIR" = "$WPCONFDIR" ]; then echo “INFO: wp-config is in $WPZIPFILE, no further downloads required” else echo “INFO: wp-config is a separate file, downloading $WPCONFIGFILE from Dropbox” sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$WPCONFIGFILE.enc #encrypted Wp-config.php file sudo openssl enc -aes-256-cbc -d -in $WPCONFIGFILE.enc -out $WPCONFIGFILE -k $ENCKEY fi

sudo rm *.enc #remove encrypted files after decryption echo -e "\n\n######### Downloaded backup files from Dropbox #########\n\n"

Section 4: Restore Wordpress files

Extract the Wordpress files into the $WPDIR, which is a variable extracted from the $WPSETTINGSFILE earlier.
#---------------------------------------------------------------------------------------
# Extracting Wordpress Files
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### Extracting Wordpress Files #########\\n\\n"

if [ -d $WPDIR ]; then echo “WARNING: Removing older version of $WPDIR” sudo rm -r $WPDIR #remove current directory (to avoid conflicts) else echo “GOOD: $WPDIR not found, proceeding to extraction” fi

echo “INFO: Extracting $WPDIR” sudo mkdir -p $WPDIR sudo tar -xzf $WPZIPFILE -C $WPDIR .

if [ "$WPDIR" = "$WPCONFDIR" ]; then echo “INFO: wp-config file is part of $WPDIR, no further action required” else echo “INFO: wp-config is a separate file, moving it to $WPCONFDIR” sudo mv $WPCONFIGFILE $WPCONFDIR echo “INFO: wp-config file moved to $WPCONFDIR” fi

echo “GOOD: Wordpress Files extracted”

Section 5: Download packages for MySQL & Restore database

Extract Wordpress configuration (database user, database name, database password) from wp-config.php, and then restore the DB based on those variables.
#---------------------------------------------------------------------------------------
# Get DB Parameters from wp-config.php
#---------------------------------------------------------------------------------------

echo “INFO: Obtaining configuration parameters from wp-config.php”

WPDBNAME=</span>cat <span style="color: #19177c;">$WPCONFDIR</span>/<span style="color: #19177c;">$WPCONFIGFILE</span> | grep DB_NAME | cut -d <span style="color: #bb6622; font-weight: bold;">\'</span> -f 4<span style="color: #ba2121;"> WPDBUSER=</span>cat <span style="color: #19177c;">$WPCONFDIR</span>/<span style="color: #19177c;">$WPCONFIGFILE</span> | grep DB_USER | cut -d <span style="color: #bb6622; font-weight: bold;">\'</span> -f 4<span style="color: #ba2121;"> WPDBPASS=</span>cat <span style="color: #19177c;">$WPCONFDIR</span>/<span style="color: #19177c;">$WPCONFIGFILE</span> | grep DB_PASSWORD | cut -d <span style="color: #bb6622; font-weight: bold;">\'</span> -f 4<span style="color: #ba2121;">

echo -e "\n\n######### Wordpress Extractiong Complete #########\n\n" #————————————————————————————— # Install MySQL and Dependencies #————————————————————————————— echo -e "\n\n######### Installing mysql Server #########\n\n" echo “INFO: Installing mysql-server” sudo -E apt-get -q -y install mysql-server >>$LOGFILE #non-interactive mysql installation

#Some security cleaning up on mysql—————————————————– sudo mysql -u root -e “DELETE FROM mysql.user WHERE User=’’;" echo “INFO: Setting password for root user to $DBPASS” sudo mysql -u root -e “UPDATE mysql.user SET authentication_string=PASSWORD(’$DBPASS’) WHERE User=‘root’;" sudo mysql -u root -e “DELETE FROM mysql.user WHERE User=‘root’ AND Host NOT IN (’localhost’, ‘127.0.0.1’, ‘::1’);" sudo mysql -u root -e “DROP DATABASE IF EXISTS test;" sudo mysql -u root -e “DELETE FROM mysql.db WHERE Db=‘test’ OR Db=‘test\_%’;" sudo mysql -u root -e “FLUSH PRIVILEGES;"

#Create DB for Wordpress with user—————————————————— echo “INFO: Creating Database with name $WPDBNAME” sudo mysql -u root -e “CREATE DATABASE IF NOT EXISTS $WPDBNAME;" echo “INFO: Granting Permission to $WPDBUSER with password: $WPDBPASS” sudo mysql -u root -e “GRANT ALL ON . TO ‘$WPDBUSER’@’localhost’ IDENTIFIED BY ‘$WPDBPASS’;" sudo mysql -u root -e “FLUSH PRIVILEGES;"

#Setup permission for my.cnf propery—————————————————- sudo chmod 644 /etc/mysql/my.cnf

#Extract mysqlfiles——————————————————————— echo “INFO: Loading $WPSQLFILE into database $WPDBNAME” sudo mysql $WPDBNAME < $WPSQLFILE -u $WPDBUSER -p$WPDBPASS #load .sql file into newly created DB

echo -e ”\n\n######### MYSQL Server Installed #########\n\n”

Section 6: Apache Configuration

Restore Apache, with 2 options:
  • $APRESTORE=1 : Restore apache configuration from backup files
  • $APRESTORE is empty: Script default Apache configuration based on wordpress directory. In this setting no further security is set for apache, you'll have to modify manually later on.
#---------------------------------------------------------------------------------------
# Basic Apache and PHP Installations
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### Installing APACHE & PHP #########\\n\\n"
echo "INFO: Installing Apache2"
sudo apt-get -y install apache2 >>$LOGFILE #non-interactive apache2 install
echo "GOOD: Apache Installed"

echo “INFO: Installing PHP and libapache2-mod-php” sudo apt-get -y install php >>$LOGFILE sudo apt-get -y install libapache2-mod-php >>$LOGFILE sudo apt-get -y install php-mcrypt >>$LOGFILE sudo apt-get -y install php-mysql >>$LOGFILE echo “GOOD: PHP Installed”

#————————————————————————————— # Loading Apache Configurations #—————————————————————————————

echo “INFO: Stopping Apache Service to load configurations” sudo service apache2 stop

if [ $APRESTORE = 1 ]; then

<span style="color: #008000;">echo</span> <span style="color: #ba2121;">"INFO: Removing configurations file--to prevent conflicts"</span>
delDir <span style="color: #19177c;">$APACHEDIR</span>
sudo mkdir -p <span style="color: #19177c;">$APACHEDIR</span>
sudo tar -xzf <span style="color: #19177c;">$APACHECONFIG</span> -C <span style="color: #19177c;">$APACHEDIR</span> .

else echo “INFO: Setting up Apache default values” echo ”### WARNING: Apache config files will not be secured ###" echo ”### Consider modifying the config files post-install ###" echo “INFO: Copying 000-default config for $DOMAIN.conf” sudo cp $SITESAVAILABLEDIR/$DEFAULTAPACHECONF $SITESAVAILABLEDIR/$DOMAIN.conf #create a temporary Apache Configuration

<span style="color: #008000;">echo</span> <span style="color: #ba2121;">"INFO: Updating $DOMAIN.conf"</span>	
sudo sed -i <span style="color: #ba2121;">"/ServerAdmin*/aServerName $DOMAIN"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#insert ServerName setting</span>
sudo sed -i <span style="color: #ba2121;">"/ServerAdmin*/aServerAlias $DOMAIN"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#insert ServerAlias setting</span>
sudo sed -i <span style="color: #ba2121;">"s|\("</span>DocumentRoot<span style="color: #ba2121;">" * *\).*|\1$WPDIR|"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#change DocumentRoot to $WPDIR</span>
sudo sed -i <span style="color: #ba2121;">"/DocumentRoot*/a&lt;Directory $WPDIR&gt;\nAllowOverride All\nOrder allow,deny\nallow from all\n&lt;/Directory&gt;"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf
sudo sed -i <span style="color: #ba2121;">"/ServerAdmin*/aServerAlias $DOMAIN"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#insert ServerAlias setting</span>

<span style="color: #408080; font-style: italic;">#Format $DOMAIN.conf file</span>
sudo sed -i <span style="color: #ba2121;">"s|\(^ServerName*\)|$EIGHTSPACES\1|"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#tab-ing</span>
sudo sed -i <span style="color: #ba2121;">"s|\(^ServerAlias*\)|$EIGHTSPACES\1|"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#tab-ing</span>
sudo sed -i <span style="color: #ba2121;">"s|\(^&lt;Directory*\)|$EIGHTSPACES\1|"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#tab-ing</span>
sudo sed -i <span style="color: #ba2121;">"s|\(^AllowOverride*\)|$EIGHTSPACES\1|"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#tab-ing</span>
sudo sed -i <span style="color: #ba2121;">"s|\(^Order*\)|$EIGHTSPACES\1|"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#tab-ing</span>
sudo sed -i <span style="color: #ba2121;">"s|\(^allow*\)|$EIGHTSPACES\1|"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#tab-ing</span>
sudo sed -i <span style="color: #ba2121;">"s|\(^&lt;/Directory*\)|$EIGHTSPACES\1|"</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#tab-ing</span>
sudo sed -i <span style="color: #ba2121;">'/#.*/ d'</span> <span style="color: #19177c;">$SITESAVAILABLEDIR</span>/<span style="color: #19177c;">$DOMAIN</span>.conf <span style="color: #408080; font-style: italic;">#remove all comments in file (nice &amp; clean!)</span>

<span style="color: #008000;">echo</span> <span style="color: #ba2121;">"INFO: Enabling $DOMAIN on Apache"</span>
sudo a2ensite <span style="color: #19177c;">$DOMAIN</span> &gt;&gt;log.txt
<span style="color: #008000;">echo</span> <span style="color: #ba2121;">"GOOD: $DOMAIN enabled, restarting Apache2 service"</span>

fi

sudo rm $APACHECONFIG #remove downloaded Apache configurations sudo a2enmod rewrite >>$LOGFILE #enable rewrite for permalinks to work sudo service apache2 start

echo “GOOD: LAMP Stack Installed!!" echo -e ”\n\n######### APACHE & PHP INSTALLED #########\n\n”

Section 7: Setup backup script

Once everything is fine, setup a backup script, and set the cronjob. Also save the encryption key for further backups to Dropbox.
#---------------------------------------------------------------------------------------
# Setup backup script & Cron jobs
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### Setting CRON Job, Swap File and Firewall #########\\n\\n"

SetCronJob #from functions.sh SetEncKey $ENCKEY ENCKEY=0 #for security reasons set back to 0

Section 8: Setup a swapfile

Some basic things we need to do, to ensure our site stability.
#---------------------------------------------------------------------------------------
# Swap File creation (1GB) thanks to peteris.rocks for this code: http://bit.ly/2kf7KQm
#---------------------------------------------------------------------------------------
sudo swapoff -a #switch of swap -- idempotency
delFile $SWAPFILE
sudo fallocate -l 1G $SWAPFILE
sudo chmod 0600 $SWAPFILE
sudo mkswap $SWAPFILE
sudo swapon $SWAPFILE
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Section 9: Setup firewall rules

Setup firewall rules to only allow port 80, port 443 and port 22. You should know what these ports are for.
#---------------------------------------------------------------------------------------
# Setup uncomplicated firewall rules for SSH, Http and Https: http://bit.ly/2kf7KQm
#---------------------------------------------------------------------------------------
sudo ufw default deny incoming
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
echo y | sudo ufw enable
echo -e "\\n\\n######### CRON jobs, firewall and swap file COMPLETE #########\\n\\n"

Section 10: Letsencrypt

Finally call letsencrypt. I call certbot, which is the default implementation from EFF. However, this part is interactive (requires user input), hence I placed it at the end.

There are ‘some’ implementations by others that don’t require user interaction–but I’d rather use the official implementation, and trust they’d continue updating it.

#---------------------------------------------------------------------------------------
# Lets encrypt
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### Let's encrypt #########\\n\\n"
#Future Feature to ping $Domain and check if IP=this machine, only then proceed
#While possible to do this automatically, I prefer to use letsencrypt supported script
sudo apt-get -y install python-letsencrypt-apache >>$LOGFILE

if [ -z "$PRODCERT” ]; then #Check for prodcert if [ $APRESTORE = 1 ]; then echo “Let’s encrypt not called, attempting to restore from backup” if [ -f $LETSENCRYPTCONFIG ]; then echo “GOOD: $LETSENCRYPTCONFIG found. Restoring configuration from backup” delDir $LETSENCRYPTDIR echo “INFO: Creating $LETSENCRYPTDIR” sudo mkdir $LETSENCRYPTDIR echo “INFO: Extracting Configuration” sudo tar -xzf $LETSENCRYPTCONFIG -C $LETSENCRYPTDIR . else echo “WARNING: Letsencrypt.tar not found, looks like you don’t have lets encrypt installed” fi else echo “WARNING: Apache wasn’t restored from Backup, unable to restore Lets Encrypt” echo “INFO: Consider installing let’s encrypt by using letsencrypt –apache” #no point copying over letsencrypt configs if Apache wasn’t restored (fresh install) fi else echo -e ”\n\n######### Getting Certs #########"

<span style="color: #666666;">(</span> crontab -l ; <span style="color: #008000;">echo</span> <span style="color: #ba2121;">"0 6 * * * letsencrypt renew"</span> <span style="color: #666666;">)</span> | crontab -
<span style="color: #666666;">(</span> crontab -l ; <span style="color: #008000;">echo</span> <span style="color: #ba2121;">"0 23 * * * letsencrypt renew"</span> <span style="color: #666666;">)</span> | crontab -

<span style="color: #008000; font-weight: bold;">if</span> <span style="color: #666666;">[</span> <span style="color: #19177c;">$PRODCERT</span> <span style="color: #666666;">=</span> 1 <span style="color: #666666;">]</span>; <span style="color: #008000; font-weight: bold;">then</span>
	<span style="color: #008000;">echo</span> <span style="color: #ba2121;">"WARNING: Obtaining production certs, these are rate-limited so be sure this is a Production server"</span>
	sudo letsencrypt --apache 
<span style="color: #008000; font-weight: bold;">else</span>
	<span style="color: #008000;">echo</span> <span style="color: #ba2121;">"Obtaining staging certs (for test)"</span>
	sudo letsencrypt --apache --staging
<span style="color: #008000; font-weight: bold;">fi</span>

fi echo -e ”\n\n######### Let’s encrypt COMPLETE #########\n\n”