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=`cat $WPCONFDIR/wp-config.php | grep DB_NAME | cut -d \' -f 4` WPDBUSER=`cat $WPCONFDIR/wp-config.php | grep DB_USER | cut -d \' -f 4` WPDBPASS=`cat $WPCONFDIR/wp-config.php | grep DB_PASSWORD | cut -d \' -f 4` 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 echo "INFO: Updating cloudflare record $CFRECORD in zone $CFZONE using credentials $CFEMAIL , $CFKEY " ./cloudflare.sh --email $CFEMAIL --key $CFKEY --zone $CFZONE --record $CFRECORD echo "INFO: Removing Cloudflare script" rm cloudflare.sh #you only need it once echo "GOOD: Cloudflare update complete" 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=`cat $WPCONFDIR/$WPCONFIGFILE | grep DB_NAME | cut -d \' -f 4` WPDBUSER=`cat $WPCONFDIR/$WPCONFIGFILE | grep DB_USER | cut -d \' -f 4` WPDBPASS=`cat $WPCONFDIR/$WPCONFIGFILE | grep DB_PASSWORD | cut -d \' -f 4` 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 echo "INFO: Removing configurations file--to prevent conflicts" delDir $APACHEDIR sudo mkdir -p $APACHEDIR sudo tar -xzf $APACHECONFIG -C $APACHEDIR . 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 echo "INFO: Updating $DOMAIN.conf" sudo sed -i "/ServerAdmin*/aServerName $DOMAIN" $SITESAVAILABLEDIR/$DOMAIN.conf #insert ServerName setting sudo sed -i "/ServerAdmin*/aServerAlias $DOMAIN" $SITESAVAILABLEDIR/$DOMAIN.conf #insert ServerAlias setting sudo sed -i "s|\("DocumentRoot" * *\).*|\1$WPDIR|" $SITESAVAILABLEDIR/$DOMAIN.conf #change DocumentRoot to $WPDIR sudo sed -i "/DocumentRoot*/a<Directory $WPDIR>\nAllowOverride All\nOrder allow,deny\nallow from all\n</Directory>" $SITESAVAILABLEDIR/$DOMAIN.conf sudo sed -i "/ServerAdmin*/aServerAlias $DOMAIN" $SITESAVAILABLEDIR/$DOMAIN.conf #insert ServerAlias setting #Format $DOMAIN.conf file sudo sed -i "s|\(^ServerName*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing sudo sed -i "s|\(^ServerAlias*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing sudo sed -i "s|\(^<Directory*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing sudo sed -i "s|\(^AllowOverride*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing sudo sed -i "s|\(^Order*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing sudo sed -i "s|\(^allow*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing sudo sed -i "s|\(^</Directory*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing sudo sed -i '/#.*/ d' $SITESAVAILABLEDIR/$DOMAIN.conf #remove all comments in file (nice & clean!) echo "INFO: Enabling $DOMAIN on Apache" sudo a2ensite $DOMAIN >>log.txt echo "GOOD: $DOMAIN enabled, restarting Apache2 service" 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 #########" ( crontab -l ; echo "0 6 * * * letsencrypt renew" ) | crontab - ( crontab -l ; echo "0 23 * * * letsencrypt renew" ) | crontab - if [ $PRODCERT = 1 ]; then echo "WARNING: Obtaining production certs, these are rate-limited so be sure this is a Production server" sudo letsencrypt --apache else echo "Obtaining staging certs (for test)" sudo letsencrypt --apache --staging fi fi echo -e "\\n\\n######### Let's encrypt COMPLETE #########\\n\\n"