Earlier this year I switched from Duplicity to rsnapshot for my local backups. Duplicity uses a full + incremental backup schema: the first time a backup is executed, all files are copied to the backup medium. Successive backups copy only the deltas of changed objects. Over time this results in a chain of deltas that need to be replayed when restoring from a backup. If a single delta is somehow corrupted, the whole chain is broke. To minimize the chances of this happening, the common practice is to complete a new full backup every so often – I usually do a full backup every 3 or 4 weeks. Completing a full backup takes time when you’re backing up hundreds of gigabytes, even over USB 3.0. It also takes up disk space. I keep around two full backups when using Duplicity, which means I’m using a little over twice as much space on the backup medium as what I’m backing up.
The backup schema that rsnapshot uses is different. The first time it runs, it completes a full backup. Each time after that, it completes what could be considered a “full” backup, but unchanged files are not copied over. Instead, rsnapshot simply hard links to the previously copied file. If you modify very large files regularly, this model may be inefficient, but for me – and I think for most users – it’s great. Backups are speedy, disk space usage on the backup medium isn’t too much more than the data being backed up, and I have multiple full backups that I can restore from.
The great strength of Duplicity – and the great weakness of rsnapshot – is encryption. Duplicity uses GnuPG to encrypt backups, which makes it one of the few solutions appropriate for remote backups. In contrast, rsnapshot does no encryption. That makes it completely inappropriate for remote backups, but the shortcoming can be worked around when backing up locally.
My local backups are done to an external, USB hard drive. Encrypting the drive is simple with LUKS and dm-crypt. For example, to encrypt /dev/sdb:
At this point, the drive will be encrypted with a passphrase. To make it easier to mount programatically, I also add a key file full of some random data generated from /dev/urandom.
There are still a few considerations to address before backups to this encrypted drive can be completed automatically with no user interaction. Since the target is a USB drive and the source is a laptop, there’s a good chance that the drive won’t be plugged in when the scheduler kicks in the backup program. If it is plugged in, the drive needs to be decrypted before calling rsnapshot to do its thing. I wrote a wrapper script called cryptshot to address these issues.
Cryptshot is configured with the UUID of the target drive and the key file used to decrypt the drive. When it is executed, the first thing it does is look to see if the UUID exists. If it does, that means the drive is plugged in and accessible. The script then decrypts the drive with the specified key file and mounts it. Finally, rsnapshot is called to execute the backup as usual. Any argument passed to cryptshot is passed along to rsnapshot. What that means is that cryptshot becomes a drop-in replacement for encrypted, rsnapshot backups. Where I previously called rsnapshot daily, I now call cryptshot daily. Everything after that point just works, with no interaction needed from me.
If you’re interested in cryptshot, you can download it directly from GitHub. The script could easily be modified to execute a backup program other than rsnapshot. You can clone my entire backups repository if you’re also interested in the other scripts I’ve written to manage different aspects of backing up data.
Tarsnap bills itself as “online backups for the truly paranoid”. I began using the service last January. It fast became my preferred way to backup to the cloud. It stores data on Amazon S3 and costs $0.30 per GB per month for storage and $0.30 per GB for bandwidth. Those prices are higher than just using Amazon S3 directly, but Tarsnap implements some impressive data de-duplication and compression that results in the service costing very little. For example, I currently have 67 different archives stored in Tarsnap from my laptop. They total 46GB in size. De-duplicated that comes out to 1.9GB. After compression, I only pay to store 1.4GB. Peanuts.
Of course, the primary requirement for any online backup service is encryption. Tarsnap delivers. And, most importantly, the Tarsnap client is open-source, so the claims of encryption can actually be verified by the user. The majority of for-profit, online backup services out there fail on this critical point.
So Tarsnap is amazing and you should use it. The client follows the Unix philosophy: “do one thing and do it well”. It’s basically like tar. It can create archives, read the contents of an archive, extract archives, and delete archives. For someone coming from an application like Duplicity, the disadvantage to the Tarsnap client is that it doesn’t include any way to automatically manage backups. You can’t tell Tarsnap how many copies of a backup you wish to keep, or how long backups should be allowed to age before deletion.
Thanks to the de-duplication and compression, there’s not a great economic incentive to not keep old backups around. It likely won’t cost you that much extra. But I like to keep things clean and minimal. If I haven’t used an online backup in 4 weeks, I generally consider it stale and have no further use for it.
To manage my Tarsnap backups, I wrote a Python script called Tarsnapper. The primary intent was to create a script that would automatically delete old archives. It does this by accepting a maximum age from the user. Whenever Tarsnapper runs, it gets a list of all Tarsnap archives. The timestamp is parsed out from the list and any archive that has a timestamp greater than the maximum allowed age is deleted. This is seamless, and means I never need to manually intervene to clean my archives.
Tarsnapper also provides some help for creating Tarsnap archives. It allows the user to define any number of named archives and the directories that those archives should contain. On my laptop I have four different directories that I backup with Tarsnap, three of them in one archive and the last in another archive. Tarsnapper knows about this, so whenever I want to backup to Tarsnap I just call a single command.
Tarsnapper also can automatically add a suffix to the end of each archive name. This makes it easier to know which archive is which when you are looking at a list. By default, the suffix is the current date and time.
Configuring Tarsnapper can be done either directly by changing the variables at the top of the script, or by creating a configuration file named tarsnapper.conf in your home directory. The config file on my laptop looks like this:
There is also support for command-line arguments to specify the location of the configuration file to use, to delete old archives and exit without creating new archives, and to execute only a single named-archive rather than all of those that you may have defined.
It makes using a great service very simple. My backups can all be executed simply by a single call to Tarsnapper. Stale archives are deleted, saving me precious picodollars. I use this system on my laptop, as well as multiple servers. If you’re interested in it, Tarsnapper can be downloaded directly from GitHub. You can clone my entire backups repository if you’re also interested in the other scripts I’ve written to manage different aspects of backing up data.
Three years ago I wrote a guide to building a VPS web server for serving sites in a PHP environment. That setup served me well for some time, but most of the sites I run now – including this one – are now written in Python. Earlier this year I built another web server to reflect this. It’s similar to before; I still use Ubuntu and I still like to serve pages with nginx. But PHP has been replaced with Python, and many of the packages used to build the environment have changed as a result. As with the last time, I decided to compile my notes into a guide, both for my own reference and in case anyone else would like to duplicate it. So far, the server has proven to be fast and efficient. It serves Python using uWSGI, uses a PostgreSQL database, and includes a simple mail server provided by Postfix. I think it’s a good setup for serving simple Django-based websites.
Basic Setup
As with last time, I recommend following Slicehost’s basic server setup article. It discusses user administration, SSH security, and firewalls. I no longer use Slicehost as my VPS provider, but I find that Slicehost’s articles provide an excellent base regardless of the host.
Packages
Packages should upgraded immediately to address any known security vulnerabilities.
12
$sudoapt-getupdate
$sudoapt-getupgrade
After the repositories have been updated, I install some essential packages.
1
$sudoapt-getinstallbuild-essentialscreendnsutils
Build-essential includes necessary tools to compile programs. I am incapable of using a computer that does not have screen on it, so that gets installed too. The third package, dnsutils, is optional, but includes dig which is useful for troubleshooting DNS issues.
DenyHosts
Slicehost’s setup article recommends turning off password authentication in SSH, forcing users to login with keys only. I use keys whenever I can, but I appreciate the option of being able to login to my server from any computer, when I may or may not have my SSH key with me. So I leave password authentication enabled. This presents the possibility of brute-force attacks. Enter DenyHosts. DenyHosts, which I have discussed previously attempts to protect against SSH attacks by banning hosts after a certain number of failed login attempts. When password authentication is enabled, running DenyHosts is a smart move.
Later on I’ll be installing some Python development packages. One of them creates a directory called /usr/lib/pymodules/python2.6/.path, which sets off a warning in chkrootkit. Part of chkrootkit’s desgin philosophy is to not include any whitelists: if chkrootkit finds something that it doesn’t like, you’re going to hear about it. I have cron run chkrootkit nightly and I want to receive any warnings, but I don’t want to receive the same false positive every morning in my inbox.
The solution is to create a file that contains chkrootkit’s warning. I call that file whitelist and store it in the same directory as chkrootkit. When chkrootkit is run, any output is redirected to a file. That file is compared to the whitelist using diff and the output of that – if any – is then read. At the end, the file containing chkrootkit’s output is deleted so that the working directory is ready for the next run. The effect is that I only hear warnings from chkrootkit that I have not explicit whitelisted. All of this can be accomplished in a single crontab entry.
The script that my cronjob runs is slightly different from the one demonstrated in the Slicehost article. Their script executes a few commands, groups the output together, and sends it to mail to email the system administrator. This results in daily emails, regardless of whether rkhunter finds any warnings or not. My script is simpler and does not result in so many messages.
The version check and update commands both have the -q switch, which disables any output – I don’t care to know whether rkhunter updated itself or not. The final line actually executes the scan. Notice that there’s no reference to mail. This script does not send any messages. The reason for that is that rkhunter itself provides the mail functionality. Inside of /etc/rkhunter.conf there is a MAIL-ON-WARNING variable. As long as the machine has an smtp server on it (which I’ll get to later in this guide), simply filling in this variable will result in any warnings being emailed to the system administrator.
Web Server
With the basics complete, it’s time to start serving something! In my previous article I covered serving a PHP-based Wordpress site via FastCGI and nginx. This time around the stack will be different: nginx, uWSGI, Python, and Django.
A few basic packages will help flesh out the server’s Python development environment:
The nginx package in Ubuntu’s official repositories is always notoriously outdated. It used to be you had to compile the server from source, but there is now an Ubuntu PPA for the latest stable versions. As described by the nginx wiki, all that is needed is to add the PPA to your sources.list and apt-get away!
If you do Python development and haven’t heard of virtualenv, it is well worth reading up on. It allows the user to create an isolated, virtual Python environment for each project. This helps immensely when developing (or serving) multiple projects on a single machine. Needless to say, I consider it to be a required package.
Install
I’ll be installing virtualenv and virtualenvwrapper (a set of scripts to facilitate working with virtual environments). I also prefer pip over easy_install for managing Python packages.
Virtual environments can be stored wherever you fancy. For now, I keep them in a hidden folder in my home directory. For these examples, I’ll setup an environment called myproject.
Notice the --no-site-packages switch. That tells virtualenv to create this environment without any of the Python packages already installed, creating a completely fresh, clean environment. The --distribute switch causes the new virtual environment to be setup with distribute, a replacement for the old and rather broken setuptools.
All that’s needed to get virtualenvwrapper up and running is to add two lines to your .bashrc and re-source the file.
We can now use commands like workon to ease the process of activating a certain environment.
I’ll go ahead and install yolk in the environment to help manage packages.
123
$workonmyproject
$pipinstallyolk
$yolk-l
The last command will cause yolk to list all packages installed in the environment. Try deactivating the environment and then running yolk again.
123
$deactivate
$yolk-l
yolk:commandnotfound
‘yolk’ wasn’t found, because it was only installed within the virtual environment. Neat!
Install Django
Finally, it’s time to install Django! The process is simple enough.
12
$workonmyproject
$pipinstalldjango
And that’s it!
The Python Imaging Library is likely to be needed for any Django project. I installed it in the beginning of this section, but because I used the --no-site-packages when creating my virtual environment, it is not available for use within the project. To fix that, I’ll just link the package in. I also previously installed psyopg2, which Python will need to communicate with my PostgreSQL database, so I’ll link that in as well. psyopg2 depends on mx, which was also previously installed but still must be made available in the environment.
While I’m still in the virtual environment, I’ll go ahead and create a new Django project. The project will have the same name as the environment: myproject. For this tutorial, I’ll stick with the precedence set by the Slicehost tutorials and use demo as the name of both my user and group on the server.
I like to keep my sites in the /srv/ directory. I structure them so that the code that runs the site, any public files, logs, and backups are all stored in separate sub-directories.
Notice that the logs and public directories were chowned to the www-data group. That is the name of the user and group that nginx will run as. The web server will need permissions to write to those locations.
Save Requirements
With the environment setup and all the necessary packages installed, now is a good time to tell pip to freeze all the packages and their versions. I keep this file in a deploy folder in my project.
Now that I have something to serve, I’ll configure uWSGI to serve it. The first step is to create a configuration file for the project. I call mine wsgi.py and store it in /srv/myproject.com/code/myproject/. It appends the current directory to the Python path, specifies the Django settings file for the project, and registers the WSGI handler.
With that done, the next step is to decide how uWSGI should be run. I’m going to use Ubuntu’s upstart to supervise the service. I keep the upstart script in my project’s deploy/ directory.
1 2 3 4 5 6 7 8 9101112131415161718
$vim/srv/myproject.com/code/deploy/uwsgi.conf
description"uWSGI server for My Project"
startonrunlevel[2345]
stoponrunlevel[!2345]
respawn
exec/usr/local/sbin/uwsgi\
--home/home/demo/.virtualenvs/myproject/\
--socket/var/run/myproject.com.sock\
--chmod-socket\
--pythonpath/srv/myproject.com/code/\
--modulemyproject.wsgi\
--process2\
--harakiri30\
--master\
--logto/srv/myproject.com/logs/uwsgi.log
Sadly, upstart doesn’t seem to recognize links. Rather than linking the config file into /etc/init/, I have to copy it.
Nginx’s configuration is pretty straight-forward. If you’ve never configured the server before, Slicehost’s articles can set you down the right path. My own nginx config looks something like this:
I keep the virtual host config for my project inside the project’s code/deploy/ directory. A basic virtual host for a Django project would looks like this:
To install and enable the virtual host, I’ll link the configuration file first to the nginx sites-available directory, and then link that link to the sites-enabled directory.
Django has a very good built-in cache framework. I like to take advantage of it with a memory-based backend: namely, memcached. It’s fast, efficient, and easy to setup.
All that’s needed is to install memcached on the server, followed by the Python API python-memcached.
The default configuration file in Ubuntu lives at /etc/memcached.conf. I usually stick with the defaults, but sometimes end up changing the port that memchached runs on or the amount of memory it is allowed to use.
I maintain a configuration file for each of the domains being served by the machine. The file for a domain lives in – you guessed it – the associated project’s deploy/ folder. Each contains two entries: one for the nginx virtual host and one for the uWSGI instance. The reason for this is that each config block needs a postrotate section to restart the associated server after the logs have been rotated. I don’t want nginx to be restarted everytime a uWSGI log is rotated, and I don’t want uWSGI restarted everytime an nginx log is rotated.
A web server isn’t much use without a database these days. I use PostgreSQL.
Install
1
$sudoapt-getinstallpostgresql
Configure
PostgreSQL has some unique terminology and ways of doing things. When I first set it up for the first time, having coming from a MySQL background, not everything was completely straightforward. As usual, Slicehost has a number of articles that will provide a foundation.
In the /etc/postgresql/8.4/main/postgresql.conf file, I uncomment the following two lines:
track_counts = on
autovacuum = on
Then restart the database server.
1
$sudo/etc/init.d/postgresql-8.4restart
After that I’ll change the password for the postgres user and the postgres database role.
If I’m restoring a previous database from a backup, now would be the time to import the backup.
1
$psql-Umyproject<myproject.postgresql
And now Django should be able to connect!
The basic server is setup and secure. Django, uWSGI, nginx and PostgreSQL are all running and getting along swimmingly. At this point, many people would be done, but I also like to have a minimal mail server.
Mail Server
Most of my domains use Google Apps, so I don’t need a full-blown mail server. I do want programs and scripts to be able to send mail, and I prefer not to do so through an external SMTP server – I’d rather just deal with having sendmail running on my own box. And I do have a few domains that do not use Google Apps. They have one or two aliases associated with them, so the server needs to receive messages for those domains and forward them off to an external address. If any of this sounds vaguely familiar, it’s because it’s the same thing I detailed last time. My setup now is the same as then, so I won’t repeat any of it here.
I use Git to keep track of the code for all my projects. (If you’re new to Git, you ought to skim the Git Reference or Everyday GIT With 20 Commands Or So). To manage websites, I create a repository of the directory with the code that runs the site (in this case, /srv/myproject.com/code/) and another empty, bare repository to work as a hub. With a post-update and post-commit, the end result is an excellent web workflow:
A copy of the hub can be checked out on a local machine for development. Whenever a change is committed, a simple git push will push the code to the web server and automatically make it live.
Changes can be made on the server in the actual live website directory. (This is not a best practice, but I do it more often than I should probably admit.) Whenever a change is committed, it is automatically pushed to the hub, so that a simple git pull is all that’s needed on the development machine to update its repository.
A more detailed explanation of this workflow is at Joe Maller’s blog.
To start, I need to create a repository for the new project I created in this tutorial. And, since this is a new server, I need to give Git my name and email address to record with every commit.
Now the hub needs a post-update script so that every time something is pushed to it, that change is automagically pulled into the live website directory.
1 2 3 4 5 6 7 8 91011121314
$vim/srv/myproject.com/hub.git/hooks/post-update
#!/bin/shechoecho"**** Pulling changes into live"echocd/srv/myproject.com/code||exitunsetGIT_DIR
gitpullhubmaster
execgit-update-server-info
$chmod+x/srv/myproject.com/hub.git/hooks/post-update
And the live website directory requires a post-commit script so that every time something is committed inside of it, that change is automagically pushed to the hub.
1 2 3 4 5 6 7 8 910
$vim/srv/myproject.com/code/.git/hooks/post-commit
#!/bin/shechoecho"**** pushing changes to Hub"echo
gitpushhub
$chmod+x/srv/myproject.com/code/.git/hooks/post-commit
All that’s left is to check out the hub onto the development machine – my laptop, in this case!
Now go back to the server, and the file should be there! To test things the other way around, I’ll delete the file from the live repository.
12345
$cd/srv/myproject.com/code/
$ls
myprojecttest
$gitrmtest
$gitcommit-m"Removing the test file"
And once again to the development machine:
123
$gitpull
$ls
deploymyproject
No more test! It’s pretty dandy.
Restoring
If I was building a new server and restoring a project from an old server, I would simply mirror the old hub and then clone that in the live directory.
Prior to building this server, I was new to a lot of this – particularly, uWSGI and virtualenv. The following tutorials helped me a good deal in putting together the perfect setup for my needs.
OpenSSH has a history of security. Only rarely are holes found in the actual program. It’s much more likely that a system will be compromised through poor configuration of the SSH daemon. Ideally, an SSH config would allow only protocol 2 connections, allow only specified users to connect (and certainly not root), disable X11 forwarding, disable password authentication (forcing ssh keys instead), and allowing connections only from specified IPs. These config options would look like this:
Protocol 2
PermitRootLogin no
AllowUsers demo
X11Forwarding no
PasswordAuthentication no
Allowing connections from only specified IP addresses would be accomplished by adding something like the following to /etc/hosts.deny:
(You could also accomplish this with iptables, but I think editing the above file is simpler.)
But the last two options – disabling password auth and allowing only certain IP addresses – limits mobility. I constantly login to my slice from multiple IPs, and I also need to login during travel when I may or may not have my key on me.
The main thing these two options protect against is a brute force attack. By allowing password logins from any IP, we give the attacker the ability to exploit the weakest part of SSH. This is where DenyHosts comes in.
DenyHosts is a python script which attempts to recognize and block brute force attacks. It has many attractive features and is included in the default Ubuntu repositories.
1
$sudoaptitudeinstalldenyhosts
The config file is located at /etc/denyhosts.conf. It is very simply and readable. I recommend reading through it, but most of the default options are acceptable. If any changes are made, the daemon must be restarted:
1
$sudo/etc/init.d/denyhostsrestart
Default Ports
Many people also advocating changing SSH’s default port to something other than 22 (more specifically, something over 1024, which won’t be scanned by default by nmap). The argument in support of this is that many automated attack scripts look for SSH only on port 22. By changing the port, you save yourself the headache of dealing with script kiddies. Opponents to changing the port would argue that the annoyance of having to specify the port number whenever using ssh or scp outweighs the minute security benefits. It’s a heated argument. I lean toward leaving SSH on the default port.
Slicehost has an excellent article repository, containing guides on a number of subjects. After building a fresh Slice, you should first follow Part 1 and Part 2 of Slicehost’s basic setup articles.
I use slightly different coloring in my bash prompt, so, rather than what Slicehost suggests in their article, I add the following to ~/.bashrc:
This is a good time to protect SSH by installing DenyHosts, which I discuss here:
1
$sudoaptitudeinstalldenyhosts
Ubuntu’s default text editor is nano, which I abhor. Real men use vim. Ubuntu comes with a slimmed down version of vim, but you’ll probably want the full version:
1
$sudoaptitudeinstallvim
To change the global default editor variable, execute the following and select the editor of your choice:
1
$sudoupdate-alternatives--configeditor
This is also a perfect time to install GNU Screen.
# Print a pretty line at the bottom of the screen
hardstatusalwayslastline
hardstatusstring'%{= kG}[ %{G}%H %{g}][%= %{=kw}%?%-Lw%?%{r}(%{W}%n*%f%t%?(%u)%?%{r})%{w}%?%+Lw%?%?%= %{g}][%{Y}%Y-%m-%d %{W}%c %{g}]'# Nobody likes startup messages
startup_messageoff
# Turn visual bell on and set the message to display for only a fraction of a second
vbellon
vbellwait.3
# Set default shell title to blank
shelltitle''# Gimme my scrollback!
defscrollback5000# Change command character to backtick
escape``# Stop programs (like vim) from leaving their contents# in the window after they exit
altscreenon
# Default screens
screen-tshell0
I prefer to have my bash profile setup to connect me to Screen as soon as I login. If there are no running sessions, it will create one. If there is a current session, it will disconnect the session from wherever it is connected and connect it to my login. When I disconnect from screen, it automatically logs me out. To achieve this, I add the following to ~/.bashrc:
12345
# If possible, reattach to an existing session and detach that session# elsewhere. If not possible, create a new session.if[-z"$STY"];thenexecscreen-dR
fi
As mentioned previously, I’ve recently moved this domain over to Slicehost. What follows is Part Three of a guide, compiled from my notes, to setting up an Ubuntu Hardy VPS. See also Part One, Part Two, and Part Four.
Now, check your email as user by running mail. See the message? Good.
Open /etc/postfix/main.cf to make sure that Postfix knows what domains it’s receiving mail for. To do this, edit the mydestination variable to include all the proper domains. For me, the name of my server looks like server.mydomain.com. I want Postfix to accept mail for that domain, but not for mydomain.com (since that’s being handled by Google Apps), so mine looks like:
Right. Now let’s send another test. Notice this time we’re using full domain names, instead of localhost:
1 2 3 4 5 6 7 8 910
$telnetserver.mydomain.com25
ehloserver.mydomain.com
mailfrom:root@server.mydomain.com
rcptto:user@server.mydomain.com
data
Subject:domains!
woot...Ithinkthisworks.
.
quit
Working? Good.
Let’s test from the outside. The first step is to open up the correct ports in the firewall. Assuming you have iptables configured in the way the Slicehost article suggests, open up your /etc/iptables.test.rules and add the following:
# Allow mail server connections
-A INPUT -p tcp -m state --state NEW --dport 25 -j ACCEPT
Now let’s apply the rules:
1
$sudoiptables-restore</etc/iptables.test.rules
Make sure everything looks dandy:
1
$sudoiptables-L
If it meets your fancy, save the rules:
12
$sudo-i
$iptables-save>/etc/iptables.up.rules
And now, from your local computer, let’s test it out.
1 2 3 4 5 6 7 8 910
$telnetserver.mydomain.com25
ehloserver.mydomain.com
mailfrom:root@server.mydomain.com
rcptto:user@server.mydomain.com
data
Subject:remoteconnectiontest
Hello,you.
.
quit
Now check your mail on the mail server as before. Once again, everything should be working.
Now we need to setup a virtual domain. Remember, I don’t want any virtual users. I only want aliases at a virtual domain to forward to my primary email address. That makes this relatively simple. (Be very, very happy. You should have seen this guide before, when I was still hosting virtual domains with virtual users!) Open up /etc/postfix/main.cf and add the following:
Create the /etc/postfix/virtual file referenced above and add the aliases:
alias@myvirtualdomain.comuser@mydomain.com
Turn it into a database:
12
$cd/etc/postfix
$sudopostmapvirtual
Restart Postfix:
1
$sudo/etc/init.d/postfixrestart
Attempt to send an email to the new alias at the virtual domain:
123456789
$telnetserver.mydomain.com25
ehloserver.mydomain.com
mailfrom:root@server.mydomain.com
rcptto:alias@myvirtualdomain.com
data
Subject:virtualdomaintest
Ihopethisworks!
.
quit
The message should now be in your primary email inbox!
As long as we’re setting up forwards, let’s forward system account mail to somewhere where it’ll actually get read. To do so, create a ~/.forward file with the following contents:
user@mydomain.com
Let’s also create a /root/.forward, so that roots mail gets forwarded to my local account (where it is then forwarded to my primary email). Root’s forward would simply read:
Now we’ve got a properly configured, but idle, box. Let’s do something with it.
Nginx is a small, lightweight web server that’s all the rage on some small corners of the Net. Apache is extremely overkill for a small personal web server like this and, since we’re limited to 256MB of RAM on this VPS, it quickly becomes a resource hog. Lighttpd is another small, lightweight web server, but I’m a fan of Nginx. Try it out.
First, we need to install the web server. Nginx is now in Ubuntu’s repositories:
Next up, we’ll need to install MySQL and PHP, and get them working with Nginx.
Slicehost has a guide for installing MySQL and Ruby on Rails, which also includes suggestions on optimizing MySQL. I follow the MySQL part of the guide, stopping at “Ruby on Rails install”.
To get PHP as FastCGI working with Nginx, we first have to spawn the fcgi process. There are a few different ways to do that. Personally, I use the spawn-fcgi app from lighttpd. To use it, we’ll compile and make lighttpd, but not install it. We’re only after one binary.
Lighttpd has a few extra requirements, so let’s install those:
1
$sudoaptitudeinstalllibpcre3-devlibbz2-dev
Now, download the source and compile lighttpd. Then copy the spawn-fcgi binary to /usr/bin/:
The script tells spawn-fcgi to launch a fastcgi process, listening on 127.0.01:9000, owned by the web user, with only 2 child processes. You may want more child processes, but I’ve found 2 to be optimal.
Give the script permissions:
1
$sudochmod+x/usr/bin/php5-fastcgi
I then link the script filename to a version-neutral, err, version:
Alright, now that PHP is running how we want it to, let’s tell Nginx to talk to it. To do that, add the following to your vhost server block in /etc/nginx/sites-available/mydomain.com, making sure to change the SCRIPT_FILENAME variable to match your directory structure:
Let’s create a file named test.php in your domain’s public root to see if everything is working. Inside, do something like printing phpinfo.
Go to http://mydomain.com/test.php. See it? Good. If you get “no input file specified” or somesuch, you broke something.
If you create an index.php, and delete any index.html or index.htm you might have, you’ll notice Nginx throws a 403 Forbidden error. To fix that, find the line in your vhost config (/etc/nginx/sites-available/mydomain.com) under the location / block that reads index index.html; and change it to index index.php index.html;. Then restart Nginx.
There is one bug in the second guide. In the first server module listening on port 443, which forwards www.domain1.com to domain1.com, the rewrite rule specifies the http protocol. So, in effect, what that rule does is forward you from a secure domain to unsecure: https://www.domain1.com to http://domain1.com. We want it to forward to a secure domain. Simply change the rewrite rule like thus:
Nothing further is needed, unless you want fancy rewrites. In that case, we’ll have to make a change to your Nginx vhost config at /etc/nginx/sites-available/mydomain.com. Add the following to your server block under location / {:
# wordpress fancy rewrites
if (-f $request_filename) {
break;
}
if (-d $request_filename) {
break;
}
rewrite ^(.+)$ /index.php?q=$1 last;
While we’re here, I usually tell Nginx to cache static files by adding the following right above thelocation / { block: