I came across a stupid problem the other day while trying to figure out why my site changes weren’t showing up.

I’m writing my problem and solution on my blog so I’ll remember and not screw up the same way again.

Consider the following virtual host files.

/etc/apache2/sites-available/example.com-05-production.vhost


    ServerName www.example.com
    ServerAlias *.example.com

    . . .

/etc/apache2/sites-available/example.com-10-staging.vhost


    ServerName staging.example.com
    ServerAlias *.staging.example.com

    . . .

My goal is to have www.example.com and anything.example.com point to one vhost with the exception of staging.example.com and anything.staging.example.com. The problem is that Apache loads the scripts in order by file name. So when the second virtual host is loaded Apache pretty much ignores it because *.example.com overrides staging.example.com.

I changed my file names of my vhost files, reloaded Apache, and all was fixed.

The correct file names:
/etc/apache2/sites-available/example.com-05-staging.vhost
/etc/apache2/sites-available/example.com-10-production.vhost

Cloud Computing & Cloud Hosting by Rackspace

Overview

This guide will help you set up the following:

  • Zend Server CE (Apache/PHP)
  • Iptables
  • SSH
  • MySQL
  • Postfix (for outgoing mail)

Some of these steps are taken from the Rackspace Cloud Server Knowledgebase.

Create a new server instance

Log in to http://manage.rackspacecloud.com and create a new server instance with Ubuntu 9.10. Any instance size is great. I’d recommend naming your server with your FQDN as it saves a few changes later on.

Securing the server

The Rackspace Cloud Server comes with the root account enabled and no firewall setup. This is not a good thing for a public server. So the first thing we will do is create a new administrator account which we will use to log in via SSH, and then we will set up Iptables as our firewall.

Rackspace will email you the IP address and password of your new server instance.

Log in over SSH to your server instance. If you have a Mac just open a terminal window and enter something like the following:

ssh root@your_server_ip

If you use windows download putty, enter the IP address in the host box and click connect.

You should now be logged in to your new Cloud Server.

The first thing we are going to do is change the root password.

Change the password by using:

passwd

Since we don’t want to log in as root anymore, we need to create a new user.

adduser admin

We want the admin user to be able to become a super user so we need to add admin to the visudo file by entering this:

visudo

Nano will open a file; add the following to the bottom of the file.

admin ALL=(ALL) ALL

Next we will make some changes to the SSH configuration file. It is also a good idea to change the port SSH uses for security. We will also disable root logins and enable admin to log in via SSH.

nano /etc/ssh/sshd_config

Port 54321
PermitRootLogin no
X11Forwarding no
UsePAM no
UseDNS no
AllowUsers admin

To make those changes take effect, restart SSH. You will not be disconnected, but if you do disconnect, you will need to reconnect using your new username and new port.

/etc/init.d/ssh restart

Firewall Configuration

This server will be a web host so very few ports will be opened.

  • HTTP 80
  • HTTPS 443
  • HTTP 10081 (Zend Server CE)
  • SSH 54321

All other ports are dropped.

Create a file named iptables.test.rules in /etc and open it using nano.

nano /etc/iptables.test.rules

Add the file lines to that file. Make changes where required.

* filter
:INPUT DROP [1:48]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [129:20352]  
  

#  Allows all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't
-A INPUT -i lo -j ACCEPT
-A INPUT -i ! lo -d 127.0.0.0/8 -j REJECT

    

#Accept SSH connections
-A INPUT -p tcp -m state --state NEW --dport 54321 -j ACCEPT    

#Accept Established connections
-A INPUT -m state --state RELATED,ESTABLISH -j ACCEPT    

#Accept HTTP connections
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 10081 -j ACCEPT

    

#Accept all PING requests on ICMP
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
    

# Reject all other inbound - default deny unless explicitly allowed policy
-A INPUT -j REJECT
-A FORWARD -j REJECT  

COMMIT

Now we are going to load the file to check for errors and to ensure the configuration is valid and our firewall works as expected.

iptables-restore < /etc/iptables.test.rules

You can view the active firewall rules by running this command.

iptables -L

If everything is satisfactory, save the rules into a new file which we will then configure to be automatically loaded upon boot.

iptables-save > /etc/iptables.up.rules

Now we need to add a line to the network interface’s initialization script so that our firewall rules will be loaded upon boot.

nano /etc/network/interfaces

Add the following line after ‘iface lo inet loopback’:

pre-up iptables-restore < /etc/iptables.up.rules

Now before we do anything else, we need to test the configuration. We don’t want to inadvertently lock ourselves out of the server, so we will test the firewall by opening a new SSH connection in a new window. As long as we don’t close our currently active connection we can still make changes if our new SSH connection fails.

If you can successfully connect to the server with the new account on the new port with the firewall rules enabled, then you should reboot the server and verify that iptables loads your configuration file on boot.

sudo reboot

You will be disconnected from both your SSH sessions.
Try reconnecting after 20 or 30 seconds, log in and then check your firewall configuration.

iptables -L

If the rules load successfully then we can move onto the next step.

Time Synchronization Setup

Run the timezone package configuration wizard selecting your time zone.

sudo dpkg-reconfigure tzdata

Create a cron job script:

sudo nano /etc/cron.daily/ntpdate

Enter the following in the /etc/cron.daily/ntpdate file:

sudo ntpdate ntp.ubuntu.com

Change permissions of the cron job script:

sudo chmod 755 /etc/cron.daily/ntpdate

Configure User Locales:

sudo locale-gen en_US.UTF-8

Configure local time zone:

sudo ln -sf /usr/share/zoneinfo/America/Toronto /etc/localtime

Outgoing Mail Server Setup

Install postfix and mail tools:

sudo apt-get install postfix mailx

MySQL Installation

Run the following command to install MySQL:

sudo apt-get install mysql-server

Zend Server CE Setup

I prefer the manual installation method. Instructions can be found here on Zend’s site.
I use a portion of those instructions in my setup.

Define a new package repository by opening the following file: /etc/apt/sources.list and adding the line:

deb http://repos.zend.com/zend-server/deb server non-free

Now download the GPG key:

sudo wget http://repos.zend.com/deb/zend.key -O- |sudo apt-key add -

Update the package list:

sudo apt-get update

Install Zend Server with PHP 5.3. Note: Zend does provide Zend Server with PHP 5.2 packages. View the Zend Server CE documentation for more information.

sudo apt-get install zend-server-ce-php-5.3

I like to install phpmyadmin but it is optional:

sudo apt-get install phpmyadmin-zend-server

I have just finished implementing Rackspace Cloud Files as the storage method for user uploaded product files on my Just1Registry online wedding registry service. I am very pleased with the results. Having the photos stored on a CDN has greatly improved page load time for my users and lessens the load on my servers.

Since the switch to Cloud Files worked so well, I thought that it would be great to use Cloud Files to store regular MySQL database dumps of my production server. I use Rackspace Cloud Servers for most of my sites, and yes, I know they have full image backups available (which I do use), but I wanted another layer of backup. More precisely, a simpler one. The only data on my servers that needs protecting is the database. The code is safe in my Springloops Subversion repository, and as I said previously, user uploaded product photos are tucked inside Cloud Files. So it seems a waste to have to restore my server to a new server instance just to grab a copy of my database.

My quest for Rackspace Cloud Files based backup solutions started where all my quests start . . . I asked Google.

I came across this article which put me on the right track, but it wasn’t the full solution I was looking for. After a little more research (specifically learning more about Duplicity), I put my own bash script together and was dumping my database to Cloud Files in no time.

The following is a step by step procedure to set up a script on Ubuntu 9.10 that will dump a MySQL database and upload the dump to Rackspace Cloud Files.

Things you’ll need:

  1. Rackspace has released source code for their API in multiple languages. In this instance we need the Python libraries. You can download python-cloudfiles here from github or use wget and the link I provide later on.
  2. Duplicity makes our job so much easier – it’s a beautifully simple tool. You could download it here but I’d recommend doing an apt-get.
  3. Get your Rackspace Cloud API key from your Rackspace Cloud account. If you don’t have an account . . .  get one!
  4. If you’re backing up a MySQL database you might want to make sure MySQL is installed on your system 🙂

The first step is to log in to your server. You will need root access for most of the following steps.
Next install duplicity.

sudo apt-get install duplicity

Next download python-cloudfiles from github. Extract the files and run setup.

wget http://github.com/rackspace/python-cloudfiles/tarball/1.7.0
tar -xzf rackspace-python-cloudfiles-8a1e850.tar.gz
cd rackspace-python-cloudfiles-8a1e850
python setup.py install

At this point, we are ready to create the script that will perform the MySQL dump and Cloud Files upload. You will need to have created a container in your Cloud Files account and you will also need your Cloud Files API Key and username.
Create the following script. Note the Cloud Files portion of this script was taken from here.

sudo nano /root/backup-mysql.sh

Contents of backup-mysql.sh

#!/bin/bash

# name of database to dump and username and password with access to that database
MYSQL_DB="mydatabase"
MYSQL_USER="username"
MYSQL_PASS="password"

#create output file name with database name, date and time
OUTPUT_PATH="/backup/mysql"
NOW=$(date +"%Y-%m-%d")
FILE=${MYSQL_DB}.$NOW-$(date +"%H-%M-%S").sql.gz

CLOUDFILES_CONTAINER="mysql-backup"
export CLOUDFILES_USERNAME=Your Cloud Files User name
export CLOUDFILES_APIKEY=API_KEY_YOU_GOT
export PASSPHRASE=The Passphrase for your encrypted backup

# dump the database and gzip it
mysqldump ${MYSQL_DB} -u ${MYSQL_USER} -p${MYSQL_PASS} | gzip -9 > ${OUTPUT_PATH}/${FILE}

duplicity ${OUTPUT_PATH} cf+http://${CLOUDFILES_CONTAINER}

Give script appropriate permissions:

sudo chmod +x /root/backup-mysql.sh

Create a cron job

sudo nano /etc/crontab

Add this line to /etc/crontab

15 * * * * root /root/backup-mysql.sh >/dev/null 2>&1

Since I already have daily backups I have set up my script to run every hour. This may be a problem in the future but seems to be fine for now. Note: The reason I am sending the output of the backup script to /dev/null is because even on successful backups Duplicity generates output which is automatically emailed to me when the job is run. I dislike getting hourly emails.
The script could use some work but it does its job at the moment.

To restore files from Cloud Files use the following script:

#!/bin/bash

# path to restore files to
OUTPUT_PATH="/backup/mysql"

CLOUDFILES_CONTAINER="mysql-backup"
export CLOUDFILES_USERNAME=Your Cloud Files User name
export CLOUDFILES_APIKEY=API_KEY_YOU_GOT
export PASSPHRASE=The Passphrase for your encrypted backup

duplicity cf+http://${CLOUDFILES_CONTAINER} ${OUTPUT_PATH}

The duplicity command has many powerful options, you can view duplicity man page here.