Click here to read the rest of my blog

The Perfect Rails/Debian/Lighttpd Stack...

Our setup starts with a minimal net-install image of Debian-3.1 Sarge. There is just the base system. Although sometimes Apache or Apache2 might be installed already. We are going to blow away Apache and only use Lighttpd to save on precious resources while getting a faster Rails server at the same time.

Ask your hosting provider to install the Webmin admin interface for your server if you want any easy way to check your email online and create users and email addresses and a lot of other admin tasks with an easy GUI. If you are a hardcore command line junky like me you can forgo this part and just use the command line like it was in the beginning ;-)

1. Create a regular user and disable root login

SSH in to your new account's IP with your root user and password and create a user for yourself.

# adduser yourusername

Get the sudo program and add yourself to the sudoers list.

# apt-get install sudo

Now edit /etc/sudoers

# pico /etc/sudoers

Add this line at the end:

yourusername ALL=(ALL) ALL

Its always better security-wise to SSH into your machine as your own user instead of root. So we will disallow root logins over SSH. And then you will use su or sudo to become root as needed.

# pico edit /etc/ssh/sshd_config

Edit this line:

PermitRootLogin yes

Change to:

PermitRootLogin no

2. Set the SSH daemon to run on a higher port

This step is entirely optional but I highly recommend it! Its a good idea to run sshd on a higher port as this will keep you safe from all the automated ssh breakin bots and skript kiddies. Most of these automated breaking attempt over ssh are done by scripts that do a port scan on your box and try to log in with ssh if they find it running. Which they will if it is on the standard port 22. Most of these attacks will not scan ports higher than 1024 in their search for sshd.

So choose a port you can live with and edit the /etc/ssh/sshd_config file to change the port that sshd runs on.

Edit this line:

Port 22

Change to:

Port 8888 # or any unused port above 1024

Then save the file and quit the editor.

Now make sshd reload its config file to pick up the new settings:

# /etc/init.d/ssh reload

3. Install GCC compilers and Ruby and friends

Now we will use our friends apt-get and apt-cache search to get our gcc c compiler toolchain so we can compile source code when needed and then move on to installing many packages.

The first thing I like to do is make a shortcut for apt-cache search because I get tired of typing that whole thing out when searching for the right packages to install. So we'll make shortcut called sapt to do that.

# pico /usr/local/bin/sapt

Add this to it and save.

#!/usr/local/bin/sapt apt-cache search $*

And chmod sapt so it is executable

# chmod +x /usr/local/bin/sapt

Let's also get rid of apache and apache2 if either was installed by the hosting provider.

# dpkg --purge apache apache2

Let's install Ruby and friends:

# apt-get install ruby1.8-dev ruby1.8 ri1.8 rdoc1.8 irb1.8 libreadline-ruby1.8 libruby1.8

Now we need to make some symlinks for basic Ruby commands.

# ln -s /usr/bin/ruby1.8 /usr/local/bin/ruby # ln -s /usr/bin/ri1.8 /usr/local/bin/ri # ln -s /usr/bin/rdoc1.8 /usr/local/bin/rdoc # ln -s /usr/bin/irb1.8 /usr/local/bin/irb

Install GCC compilers and Docs.

# apt-get install gcc-3.4-doc gcc-3.4 g++-3.4 make

We also need a few symlinks these to work for compiling lighttpd which is written in C++

# ln -s /usr/bin/g++-3.4 /usr/bin/g++ # ln -s /usr/bin/gcc-3.4 /usr/bin/gcc

4.Install MySQL (and Postfix)

Lets install Postfix here really quick. We will configure it later but if we don't install it before we install MySQL, apt will pull in Exim as a dependency. We don't want this so lets pre-empt it by installing Postfix.

# apt-get install postfix

The Postfix installer will ask you four questions. Just choose or enter the following answers:

  1. yourusername
  2. Your default.com domain
  3. example.com, localhost.com, , localhost
  4. No

Now let's install MySQL and friends.

# apt-get install mysql-server-4.1 mysql-common-4.1 \ mysql-client-4.1 libmysqlclient14-dev libmysqlclient14 mytop zlib1g-dev

When prompted, answer: OK

Now let's set the root password for our mysql database.

# mysqladmin -u root password yourrootsqlpassword

Install the MySQL bindings.

# apt-get install libmysql-ruby1.8

Now test it:

# irb irb(main):001:0> require 'mysql' => true irb(main):002:0> exit

5. Install RubyGems and Rails

Now we will install RubyGems so we can get Rails and all kinds of other Ruby treasures!

# wget http://rubyforge.org/frs/download.php/3700/rubygems-0.8.10.tgz # tar xvzf rubygems* # cd rubygems* # ruby setup.rb

Let's get rid of the rubygems source directory to save space for later.

# cd .. # rm -rf rubygems-0.8.10 rubygems-0.8.10.tgz

Now we'll install the latest version of Rails.

# gem install rails --include-dependencies

You should see:

Attempting local installation of 'rails' Local gem file not found: rails*.gem Attempting remote installation of 'rails' Updating Gem source index for: http://gems.rubyforge.org Successfully installed rails-0.14.3 Successfully installed rake-0.6.2 Successfully installed activesupport-1.2.3 Successfully installed activerecord-1.13.0 Successfully installed actionpack-1.11.0 Successfully installed actionmailer-1.1.3 Successfully installed actionwebservice-0.9.3 Installing RDoc documentation for rake-0.6.2... Installing RDoc documentation for activesupport-1.2.3... Installing RDoc documentation for activerecord-1.13.0... lib/active_record.rb:73:64: Skipping require of dynamic string: "active_record/connection_adapters/#{adapter}_adapter" Installing RDoc documentation for actionpack-1.11.0... lib/action_controller/assertions.rb:4:69: Skipping require of dynamic string: "#{File.dirname(__FILE__)}/vendor/html-scanner/html/document" Installing RDoc documentation for actionmailer-1.1.3... Installing RDoc documentation for actionwebservice-0.9.3...

Don't worry about those couple of errors. That just happens sometimes when building the RDoc documentation. If you look closely, you will see that it says "Successfully installed foobar..." a bunch of times.

Alright, we've already made it pretty far! So far we have Ruby on Rails with MySQL and Postfix installed. Lets move on to Lighttpd and the FastCGI stuff.

6. Install Lighttpd and FastCGI

Get the fcgi-dev headers so we can compile lighttpd.

# apt-get install libfcgi-dev

Now let's install the ruby-fcgi bindings from source so we know we have the good ones.

wget http://sugi.nemui.org/pub/ruby/fcgi/ruby-fcgi-0.8.6.tar.gz tar xzvf ruby-fcgi* cd ruby-fcgi-0.8.6 ruby install.rb config ruby install.rb setup sudo ruby install.rb install

And let's make sure it works.

# irb irb(main):001:0> require 'fcgi' => true irb(main):002:0> exit

Cleanup the ruby-fcgi source code.

# cd .. # rm -rf ruby-fcgi-0.8.6 ruby-fcgi-0.8.6.tar.gz

Alright cool, on to Lighty! First we need the PCRE (Perl Compatible Regular Expression). Lighty needs these for mod_rewrite and config file processing.

# wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-5.0.tar.gz # tar xzvf pcre-5.0.tar.gz # cd pcre-* # ./configure # make # make test # make install

And cleanup the source.

# cd .. # rm -rf pcre-5.0*

Get the Lighty source.

# wget http://www.lighttpd.net/download/lighttpd-1.4.7.tar.gz # tar xzvf lighttpd* # cd light* # ./configure

You should now see this after it gets done scrolling by:

Plugins: enabled: mod_access mod_accesslog mod_alias mod_auth mod_cgi mod_compress mod_dirlisting mod_evhost mod_expire mod_fastcgi mod_indexfiles mod_proxy mod_redirect mod_rewrite mod_rrdtool mod_scgi mod_secdownload mod_setenv mod_simple_vhost mod_ssi mod_staticfile mod_status mod_trigger_b4_dl mod_userdir mod_usertrack mod_webdav disabled: mod_cml mod_mysql_vhost Features: enabled: auth-crypt compress-deflate compress-gzip large-files network-ipv6 regex-conditionals disabled: auth-ldap compress-bzip2 network-openssl stat-cache-fam storage-gdbm storage-memcache webdav-properties

If you get that then you are all set. You want to make sure that you get the lines in the "enabled" section:
mod_fastcgi
mod_rewrite
mod_scgi
mod_simple_vhost

Now on with lighttpd:

# make # make install

Now test to make sure you have Lighty and that it's in your $PATH:

# lighttpd 2005-11-13 04:20:56: (server.c.364) No configuration available. Try using -f option.

Thats the error we want to see! That means that lighttpd is working and installed correctly but just can't find a config file because you didn't specify one.

Let's clean up after ourselves.

# cd .. # rm -rf light*

7. Install Subversion, ImageMagick and RMagick

So that brings us into a pretty good stack. But we still need Subversion and Postfix config. Also why don't we just install ImageMagick and RMagick while we're at it huh? This thing gives everyone trouble so lets do it and be done with it because its really cool and eventually you will want to use it in one of your Rails apps.

# apt-get install imagemagick librmagick-ruby1.8 librmagick-ruby-doc libfreetype6-dev xml-core

Say yes to the install question - you don't need any of the suggested packages. Remember, we are shooting for lightweight!

So now let's test that RMagick works.

# irb irb(main):001:0> require 'RMagick' => true irb(main):002:0> exit

Sweet! We have a great Rails stack now from Ruby->Rails->MySQL and bindings->Lighttpd->FCGI and bindings->RMagick too! So let's get Subversion now and then we'll configure Postfix in the next section.

# apt-get install subversion subversion-tools libsvn0-dev libsvn0

Answer:

yes

8. Configure the Postfix mail server

OK lets get to the Postfix set up. We will install Postfix with a pop3 server and imap ple sasl auth and tls for smtp. This will give us a secure lightweight mail server with authenticated pop and smtp: Postfix/POP3/IMAP

In order to install Postfix with SMTP-AUTH and TLS as well as a POP3 server that also does POP3s (port 995) and an IMAP server that is also capable of IMAPs (port 993) do the following steps:

apt-get install postfix postfix-tls libsasl2 \ sasl2-bin libsasl2-modules ipopd-ssl uw-imapd-ssl <- pop3 and pop3s <- No <- Internet Site <- NONE <- server1.example.com <- server1.example.com, localhost.example.com, localhost <- No postconf -e 'smtpd_sasl_local_domain =' postconf -e 'smtpd_sasl_auth_enable = yes' postconf -e 'smtpd_sasl_security_options = noanonymous' postconf -e 'broken_sasl_auth_clients = yes' postconf -e 'smtpd_recipient_restrictions = \ permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination' postconf -e 'inet_interfaces = all' echo 'pwcheck_method: saslauthd' >> /etc/postfix/sasl/smtpd.conf echo 'mech_list: plain login' >> /etc/postfix/sasl/smtpd.conf mkdir /etc/postfix/ssl cd /etc/postfix/ssl/ openssl genrsa -des3 -rand /etc/hosts -out smtpd.key 1024 chmod 600 smtpd.key openssl req -new -key smtpd.key -out smtpd.csr openssl x509 -req -days 3650 -in smtpd.csr -signkey smtpd.key -out smtpd.crt openssl rsa -in smtpd.key -out smtpd.key.unencrypted mv -f smtpd.key.unencrypted smtpd.key openssl req -new -x509 -extensions v3_ca -keyout cakey.pem -out cacert.pem -days 3650 postconf -e 'smtpd_tls_auth_only = no' postconf -e 'smtp_use_tls = yes' postconf -e 'smtpd_use_tls = yes' postconf -e 'smtp_tls_note_starttls_offer = yes' postconf -e 'smtpd_tls_key_file = /etc/postfix/ssl/smtpd.key' postconf -e 'smtpd_tls_cert_file = /etc/postfix/ssl/smtpd.crt' postconf -e 'smtpd_tls_CAfile = /etc/postfix/ssl/cacert.pem' postconf -e 'smtpd_tls_loglevel = 1' postconf -e 'smtpd_tls_received_header = yes' postconf -e 'smtpd_tls_session_cache_timeout = 3600s' postconf -e 'tls_random_source = dev:/dev/urandom'

The file /etc/postfix/main.cf should now look like this:

________________________________________________________ # See /usr/share/postfix/main.cf.dist for a commented, more complete version smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) biff = no # appending .domain is the MUA's job. append_dot_mydomain = no # Uncomment the next line to generate "delayed mail" warnings #delay_warning_time = 4h myhostname = server1.example.com alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases myorigin = /etc/mailname mydestination = server1.example.com, localhost.example.com, localhost relayhost = mynetworks = 127.0.0.0/8 mailbox_command = procmail -a "$EXTENSION" mailbox_size_limit = 0 recipient_delimiter = + inet_interfaces = all smtpd_sasl_local_domain = smtpd_sasl_auth_enable = yes smtpd_sasl_security_options = noanonymous broken_sasl_auth_clients = yes smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination smtpd_tls_auth_only = no smtp_use_tls = yes smtpd_use_tls = yes smtp_tls_note_starttls_offer = yes smtpd_tls_key_file = /etc/postfix/ssl/smtpd.key smtpd_tls_cert_file = /etc/postfix/ssl/smtpd.crt smtpd_tls_CAfile = /etc/postfix/ssl/cacert.pem smtpd_tls_loglevel = 1 smtpd_tls_received_header = yes smtpd_tls_session_cache_timeout = 3600s tls_random_source = dev:/dev/urandom -------------------------------------------------------

Now restart Postfix.

# /etc/init.d/postfix restart

Authentication will be done by saslauthd. We have to change a few things to make it work properly. Because Postfix runs chrooted in /var/spool/postfix we have to do the following:

# mkdir -p /var/spool/postfix/var/run/saslauthd # rm -fr /var/run/saslauthd

Now we have to edit /etc/default/saslauthd in order to activate saslauthd. Remove # in front of START=yes and add the line PARAMS="-m /var/spool/postfix/var/run/saslauthd".

The file should now look like this:

-------------------------------------------------------- # This needs to be uncommented before saslauthd will be run automatically START=yes PARAMS="-m /var/spool/postfix/var/run/saslauthd" # You must specify the authentication mechanisms you wish to use. # This defaults to "pam" for PAM support, but may also include # "shadow" or "sasldb", like this: # MECHANISMS="pam shadow" MECHANISMS="pam" --------------------------------------------------------

Finally, we have to edit /etc/init.d/saslauthd.

Edit this line:

dir=`dpkg-statoverride --list $PWDIR`

Change to:

#dir=`dpkg-statoverride --list $PWDIR`

Then change the variables PWDIR and PIDFILE and add the variable dir at the beginning of the file like this:

PWDIR="/var/spool/postfix/var/run/${NAME}" PIDFILE="${PWDIR}/saslauthd.pid" dir="root sasl 755 ${PWDIR}"

/etc/init.d/saslauthd should now look like this:

-------------------------------------------------------- #!/bin/sh -e NAME=saslauthd DAEMON="/usr/sbin/${NAME}" DESC="SASL Authentication Daemon" DEFAULTS=/etc/default/saslauthd PWDIR="/var/spool/postfix/var/run/${NAME}" PIDFILE="${PWDIR}/saslauthd.pid" dir="root sasl 755 ${PWDIR}" createdir() { # $1 = user # $2 = group # $3 = permissions (octal) # $4 = path to directory [ -d "$4" ] || mkdir -p "$4" chown -c -h "$1:$2" "$4" chmod -c "$3" "$4" } test -f "${DAEMON}" || exit 0 # Source defaults file; edit that file to configure this script. if [ -e "${DEFAULTS}" ]; then . "${DEFAULTS}" fi # If we're not to start the daemon, simply exit if [ "${START}" != "yes" ]; then exit 0 fi # If we have no mechanisms defined if [ "x${MECHANISMS}" = "x" ]; then echo "You need to configure ${DEFAULTS} with mechanisms to be used" exit 0 fi # Add our mechanimsms with the necessary flag PARAMS="${PARAMS} -a ${MECHANISMS}" START="--start --quiet --pidfile ${PIDFILE} --startas ${DAEMON} --name ${NAME} -- ${PARAMS}" # Consider our options case "${1}" in start) echo -n "Starting ${DESC}: " #dir=`dpkg-statoverride --list $PWDIR` test -z "$dir" || createdir $dir if start-stop-daemon ${START} >/dev/null 2>&1 ; then echo "${NAME}." else if start-stop-daemon --test ${START} >/dev/null 2>&1; then echo "(failed)." exit 1 else echo "${DAEMON} already running." exit 0 fi fi ;; stop) echo -n "Stopping ${DESC}: " if start-stop-daemon --stop --quiet --pidfile "${PIDFILE}" \ --startas ${DAEMON} --retry 10 --name ${NAME} \ >/dev/null 2>&1 ; then echo "${NAME}." else if start-stop-daemon --test ${START} >/dev/null 2>&1; then echo "(not running)." exit 0 else echo "(failed)." exit 1 fi fi ;; restart|force-reload) $0 stop exec $0 start ;; *) echo "Usage: /etc/init.d/${NAME} {start|stop|restart|force-reload}" >&2 exit 1 ;; esac exit 0 -----------------------------------------------------------------

Now start saslauthd:

/etc/init.d/saslauthd start

To see if SMTP-AUTH and TLS work properly now run the following command:

telnet localhost 25

After you have established the connection to your Postfix mail server, type this:

ehlo localhost

If you see the lines:

250-STARTTLS

and:

250-AUTH

everything is fine. Type quit to get out of telnet.

9. Install QPopper for POP3 mail

Now we need to install qpopper, a pop3 server. When you install this, it will tell you that it's removing some packages specifically related to ipopd

# apt-get install qpopper

Answer:

yes

10. Test it and tweak it!

Now you should be in great shape for Rails development. Lets generate a skeleton Rails app and fire it up with Lighttpd to make sure everything is working fine. Lets switch to our user now as we don't need root for Rails development.

# su yourusername # cd ~ # rails test

You need to add /usr/local/sbin to your path to run Lighttpd.

Edit ~/.bashrc

$ pico ~/.bashrc

Add this line at the end:

export PATH=/usr/local/sbin:$PATH

Now save and quit.

OK lets try out our rails test project!

# cd test # ruby script/server

Since we are running rails 1.0rc release or later, the script/server command will generate a lighttpd.conf file in RAILS_ROOT/config/lighttpd.conf and start Lighttpd with it. If rails can't find Lighttpd it will boot WEBrick instead. If it boots WEBrick, make sure Lighttpd is in your $PATH and look back over the instructions to make sure you have followed them correctly.

If it started fine then take a look at:
http://your.ip.add.ress:3000

You should see the Congratulations, you've put Ruby on Rails! Page and we are done with the Perfect Debian Sarge Rails Stack Server! Enjoy your new sandboxed environment that no-one else can mess up like on a shared host.

Actually I lied a little bit. We need to install the Webmin module for Postfix and a few other niceties. This is only if you decided to have Webmin installed. Our lightweight powerhouse Rails install wants to be as light as possible so if you don't need Webmin leave it off.

$ sudo apt-get install webmin-postfix webmin-mysql webmin-mailboxes \ webmin-logrotate webmin-firewall webmin-status

That should give you a great amount of flexibility out of your webmin control panel. You can reach your control panel with the following address:
http://your.ip.add.ress:10000

Log in with the root username/password and go to town tweaking your settings as much as you want!

Now lets see how much space we took up for our ultimate lightweight Rails server system.

Become root again.

# sudo -s

Then run these commands:

# cd / # du -h

Look at the last number after all the text scrolls by. For me during this install the grand total is: 773M

Hey thats not bad! 773MB for the entire Rails/MySQL/Lighttpd stack with Subversion and Postfix installed and configured. If you did this on a 4Gig VPS then you still have over 3Gigs left for data and Rails apps.

Now there is more you can do to tweak this system such as tuning MySQL for performance with less memory. Depending on how much SQL your rails apps are going to be using you might want to run a few less MySQL processes than the default. Or you might want to go with PostgreSQL or SQLite. Thats a bit beyond how long I want to make this tutorial. But I hope this helps you get the most out of Rails and a VPS server. I know that this is the way to go for hosting Rails apps and developing them as well.

Now you will have all your code that you have checked into SVN available anywhere you go and your Rails apps, email and other services will not go down or be affected by other users on the same shared host. This is a very nice environment because you know that you are the only one there and all the resources are yours. No beginners with misconfigured apps running 40 FCGI processes or anything else to ruin your day and your uptime.

You will need to create new SVN repositories and since we don't have Apache bloat installed, the best way to run SVN over the network is with svnserve or over svn+ssh. I'll leave that as an exercise for the reader as well as whatever else you want to do to customize your new $HOME.