HOWTO Install Typo on FreeBSD with Lighttpd, FastCGI, and Apache2 Proxy

Okay, I've spent some time configuring one of my FreeBSD servers to host multiple instances of Typo running via FastCGI processes ala Lighttpd, proxied by Apache2, each as a dedicated user. Like everything, there's more than one way to do it and somebody else probably has figured out a more better, more slicker way, but this is how I'm doing it at present. Your mileage may vary. Caveat emptor, etc.

Installing Required Software

The easiest way to install all of the required software on FreeBSD is to install them via the FreeBSD Ports Collection. If you need to learn how to use Ports, visit the FreeBSD Handbook page on Using the Ports Collection.

Note: Ken just spent a lot of time tracking down an issue w/help of pdcawley (thanks bunches bud!). If you encounter ruby dumping core, bus errors and error.log entries looking like this:

2006-03-18 15:30:50: (mod_fastcgi.c.2430) unexpected end-of-file (perhaps the fastcgi process died): pid: 27650 socket: unix:/tmp/typo-yourblog.socket-1 
2006-03-18 15:30:50: (mod_fastcgi.c.3172) child signaled: 4 
2006-03-18 15:30:50: (mod_fastcgi.c.3215) response not received, request sent: 982 on socket: unix:/tmp/typo-youblog.socket-1 for /dispatch.fcgi , closing connection 

then use the ruby-nopthreads port.

Install Apache2 with mod_proxy modules built in (the port disables this by default, so it needs to be implicitly called)

cd /usr/ports/www/apache2
make WITH_PROXY_MODULES=yes install clean

Then, to insure that Apache2 is automatically built with mod_proxy modules in the future

echo "WITH_PROXY_MODULES=yes" >> /etc/make.conf

Install Ruby On Rails

cd /usr/ports/www/rubygem-rails; make install clean

If you want Textile formatting (if you don't know, you do) install Redcloth

cd /usr/ports/www/rubygem-redcloth; make install clean

Install FastCGI library for Ruby

cd /usr/ports/www/ruby-fcgi; make install clean

Install FastCGI Development Kit

cd /usr/ports/www/fcgi; make install clean

Install mod_fcgid

cd /usr/ports/www/mod_fcgid; make install clean

in /usr/local/etc/apache2/httpd.conf add the following to the 'LoadModule?' section:

LoadModule fcgid_module libexec/apache2/mod_fcgid.so

  <IfModule mod_fcgid.c>
    AddHandler fcgid-script .fcgi
  </IfModule>

Restart apache2

apache2ctl restart

If there were no errors written to screen, ensure that none were written to log

tail -n 50 /var/log/http-*

If there are no errors, continue on to Installing Typo.

Installing Typo

Install Typo Stable

or

Install Typo Current from Trunk using Subversion (NOTE if you need subversion, install it with 'cd /usr/ports/devel/subversion; make install clean')

svn checkout svn://typosphere.org/typo/trunk typo

Configure a directory scheme. I use the follwoing:

/home/username/websites/typo.somedom.tld

Copy Typo to your target directory

cp -R typo /home/username/websights/typo.somedom.tld

Set ownership to your user

chown -R username:username /home/username/websites/typo.somedom.tld

Configuring Typo

There are some Typo files needing modification. FreeBSD's bash port is installed in /usr/local/bin/bash rather than /usr/bin/bash. Later versions of Typo use /bin/sh but you should double check. I also had to modify some other paths of a few files in public to get my statup scripts to work during reboot. It was a bit perplexing for a while there, as I could run them manually from command line, but they failed upon actual reboot.

In any event, the fix was relacing

/usr/bin/env ruby 

with

/usr/local/bin/ruby 

in public/dispatch.fcgi and public/dispatch.rb.

(This is because /usr/local/bin isn't in your path when lighttpd runs. Try explicitly appending it to your path in the lighttpd init script)

Database

Assume you can create database and set access as appropriate. I'm using MySQL, e.g.:

mysql -p
create database username_typo;
grant all on username_typo.* to 'username'@'localhost' \
identified by 'somekillerpassword';
flush privileges;
use username_typo;
source /path/to/db/schema.mysql.sql;

Edit config/database.yml as appropriate to mesh with above.

Lighttpd

Edit config/lighttpd.conf. Specify bind address and port, username, groupname, etc., e.g.:

server.pid-file             = "/var/run/typo-username.pid"
server.port                 = 3000
server.bind                 = "127.0.0.1"
server.event-handler        = "freebsd-kqueue"
server.name                 = "typo.username.com"
server.username             = "username"
server.groupname            = "username"

server.modules              = ( "mod_rewrite", "mod_fastcgi", )
server.indexfiles           = ( "dispatch.fcgi" )
server.document-root        = "/home/username/websites/typo.username.com/public/"
server.error-handler-404    = "/dispatch.fcgi"
server.errorlog             = "/home/username/websites/typo.username.com/log/error.log"

url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" )

#### fastcgi module
fastcgi.server =  (
   ".fcgi" => (
     "typo" => (
       "min-procs" => 4,
       "max-procs" => 4,
       "socket" => "/home/username/tmp/typo-username.socket",
       "bin-path" => "/home/username/websites/typo.username.com/public/dispatch.fcgi",
                "bin-environment" => ("RAILS_ENV" => "production" ),
       "idle-timeout" => 120
     )
   )
)

Test Your Configuration

./script/lighttp &
netstat -na | grep 3000
kobuk# netstat -an | grep 300
tcp4       0      0  127.0.0.1.3000         *.*                    LISTEN

Open a browser to http://127.0.0.1:3000

Note fro Ken: Above was edited from original author's text. Original author does not run a web browser on the server running their blogs. Initial text assumed you have access to the port, e.g. 3000 on the server in question. So I stand by my original instructions:

Point your web browser to typo.username.com. Or whatever your server is named;-)

If you're not seeing your bright shinny new blog, enable vebose logging in your my.cnf and make sure typo is actually connecting to MySQL. Assuming you've gotten this far and all is well, I move config/lighttp.conf to /usr/local/etc/username_typo.conf because I don't want my end users tweakig their config and doiong lame things like setting min-procs to 2000....

Apache2 mod_proxy

To get Apache2 set up to proxy incoming requests on port 80 you need to modify /usr/local/etc/apache/httpd.conf

<VirtualHost xxx.xxx.xxx.xxx:80>
    ServerAdmin                 webmaster@username.tld
        ServerName              typo.username.tld:80
        ProxyRequests           Off
        ProxyPreserveHost       On
        RewriteEngine           On
        RewriteRule             ^/(.*) http://127.0.0.1:3000/$1 [P,L]
        ProxyPassReverse        / http://127.0.0.1:3000/
</VirtualHost>

Don't forget the ProxyPreserveHost else everything will look like it's working but routes will see 127.0.0.1:3004 as incoming hostname and use it to write all the internal rss links.

Restart Apache2

apache2ctl restart

You should now be able to access your Typo site via port 80.

Start up script

Now we need to get a start up script happening to start/stop Typo on system srtart/shudown. There's lots of ways to do this but I took a stab at doing it at least minimally conrrectly for FreeBSD by using /usr/local/etc/rc.d and /etc/rc.conf.local. You could do lots better than this, but here's my modified lighttpd.sh:

#!/bin/sh
#
# $FreeBSD: ports/www/lighttpd/files/lighttpd.sh.tmpl,v 1.3 2005/02/06 16:30:35 sem Exp $
#

# PROVIDE: lighttpd
# REQUIRE: DAEMON
# BEFORE: LOGIN
# KEYWORD: FreeBSD shutdown

# kg rc.d script to start instance of typo running under FastCGI via lighttpd
# as dedicated user on dedicated port, e.g. 3000.  Then proxy via Apache
#
# copy lighttp.conf included with typo distribution to /usr/local/etc
# and modify as necessary to suit your setup
#
# Add the following lines to /etc/rc.conf or /etc/rc.conf.local
#
#typo_${blogname}_enable="YES"
#typo_${blogname}_conf="/usr/local/etc/typo${blogname}.conf"
#typo_${blogname}_chdir="/path/to/the/typo/instance/you/wish/to/run/"
#
#

. /etc/rc.subr

name=typo_username
rcvar=`set_rcvar`

# specify here just incase we space out and forget to put it in lighty config.

RAILS_ENV=production
export RAILS_ENV

command=/usr/local/sbin/lighttpd
pidfile=/var/run/typo-username.pid
required_files=${typo_username_conf}

stop_postcmd=stop_postcmd

stop_postcmd()
{
  rm -f $pidfile
}

# set defaults

typo_username_enable=${typo_username_enable:-"NO"}
typo_username_conf=${typo_username_conf:-"/usr/local/etc/typo_username.conf"}
typo_username_chdir=${typo_username_chdir:-"/nonexistent/"}

load_rc_config $name

command_args="-f ${typo_username_conf}"
run_rc_command "$1"

Then in /etc/rc.conf.local:

typo_username_enable="YES"
typo_username_chdir="/home/username/websites/typo.username.tld/"

I just create a separate typo_username.sh for each blog. It would be much slicker to have this all in one .sh file and loop through the typo instances, e.g. the way FreeBSD's /usr/local/etc/rc.d/zope.sh does but I'm not a rc.d scripting guru so it will have to wait until I have more time....

Conclusion

And that's about it. Repeat as necessary. End result is multiple instances of Typo, each running as separate user, on dedicated high numbered port via FastCGI all being proxied by an Apache front end. This is quick and dirty wiki page. I'm sure I made a typo here or there (no pun intended) but xal asked me to post it and I wanted to give something back. Please give it some scrutiny and use some common sense.... Questions? Comments?? Author is Ken Gunderson and can be reached via kgunders at don't spam me unless you want my mail server to blacklist you forever teamcool.net. Happy Typo'ing on FreeBSD. Now I really must get some sleeep...;-)

Benchmarks

Okay, had a couple requests for some benchmarks so here's some quick and dirty ab's. Apache2 is straight out of the ports collection and no effort whatsoever invested in tuning and tweaking for performance. Typo version is also pre new and improved and way cool admin interface. Lighty is configured to spawn a dozen FastCGI procs. Machine hardware:

dual AMD Operon 246
4GB DDR400 ECC Registered Memory
System:
5.4-RELEASE-p5 FreeBSD amd64  (could probably increase perf by running in 32 bit mode).

Relevant Ports:

ruby-1.8.2_4        An object-oriented interpreted scripting language
ruby18-bdb1-0.2.2   Ruby interface to Berkeley DB revision 1.8x with full featu
ruby18-fcgi-0.8.6   FastCGI library for Ruby
ruby18-gems-0.8.11  Package management framework for the Ruby language
ruby18-iconv-1.8.2  An iconv wrapper class for Ruby
mysql-client-4.1.13 Multithreaded SQL database (client)
mysql-server-4.1.13 Multithreaded SQL database (server)
apache-2.0.54_2     Version 2 of Apache web server with prefork MPM.

1. Start out w/concurrency of 1:

ab -n 10000 -c 1 http://192.168.1.177:80/

Server Software:        lighttpd/1.3.15
Server Hostname:        192.168.1.177
Server Port:            80

Document Path:          /
Document Length:        2822 bytes

Concurrency Level:      1
Time taken for tests:   1216.168115 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      30380000 bytes
HTML transferred:       28220000 bytes
Requests per second:    8.22 [#/sec] (mean)
Time per request:       121.617 [ms] (mean)
Time per request:       121.617 [ms] (mean, across all concurrent requests)
Transfer rate:          24.39 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       4
Processing:    18  120  14.2    118     235
Waiting:       17  120  14.0    117     234
Total:         18  120  14.2    118     235

Percentage of the requests served within a certain time (ms)

  50%    118
  66%    118
  75%    118
  80%    118
  90%    118
  95%    172
  98%    175
  99%    176
 100%    235 (longest request)

Now let's jack concurency up a bit....

$ ab -n 10000 -c 10 http://192.168.1.177:80/

Concurrency Level:      10
Time taken for tests:   146.104187 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      30380000 bytes
HTML transferred:       28220000 bytes
Requests per second:    68.44 [#/sec] (mean)
Time per request:       146.104 [ms] (mean)
Time per request:       14.610 [ms] (mean, across all concurrent requests)
Transfer rate:          203.05 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       2
Processing:    18  145  50.9    126     601
Waiting:       18  143  47.5    125     601
Total:         18  145  50.9    126     601

Percentage of the requests served within a certain time (ms)
  50%    126
  66%    141
  75%    157
  80%    169
  90%    204
  95%    237
  98%    293
  99%    352
 100%    601 (longest request)

And some more...

$ ab -n 10000 -c 100 http://192.168.1.177:80/

Concurrency Level:      100
Time taken for tests:   129.502056 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      30383038 bytes
HTML transferred:       28222822 bytes
Requests per second:    77.22 [#/sec] (mean)
Time per request:       1295.021 [ms] (mean)
Time per request:       12.950 [ms] (mean, across all concurrent requests)
Transfer rate:          229.11 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   1.4      0      18
Processing:    52 1286 205.9   1275    2403
Waiting:       51 1285 205.5   1274    2402
Total:         69 1286 205.5   1275    2403

Percentage of the requests served within a certain time (ms)
  50%   1275
  66%   1342
  75%   1390
  80%   1424
  90%   1536
  95%   1645
  98%   1777
  99%   1854
 100%   2403 (longest request)

For sake of comparison, on a more modest legacy machine, PIII-700 w/1GB Ram, FBSD-5.4-p5 i386, and max-proc=4:

$ ab -n 1000 -c 1000 http://typo.mydom.tld/

Document Path:          /
Document Length:        7951 bytes

Concurrency Level:      1000
Time taken for tests:   7.122140 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      8750272 bytes
HTML transferred:       8493032 bytes
Requests per second:    140.41 [#/sec] (mean)
Time per request:       7122.140 [ms] (mean)
Time per request:       7.122 [ms] (mean, across all concurrent requests)
Transfer rate:          1199.78 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        2  434 1091.0      2    6205
Processing:   133 1036 295.6   1034    2152
Waiting:      130  788 209.6    805    1381
Total:        287 1471 1126.9   1108    7076

Percentage of the requests served within a certain time (ms)
  50%   1108
  66%   1288
  75%   1378
  80%   1451
  90%   3872
  95%   4146
  98%   4394
  99%   4517
 100%   7076 (longest request)

Hmmm.... 140 requests/sec from 4 FastCGI procs doesn't look too shabby to me, eh? But then what do I know....;-P

Am I reading this wrong, or does the PIII look to be much faster than the amd 246? I guess you'd need to have -c 1000 on the amd to get a more direct comparison.

Response from Ken on 11/25/2005-- Yes, the lowly PIII 700 aoutperformed the dual opteron in these tests. Obviously the Opteron machine is going to scale far beyond the PIII 700. I'm not sure about they precise reasons but I've heard some discussion that the 32 bit code has been highly optimized over time compared to the 64 bit stuff. Don't quote me on it though. The 64 bit stuff really comes into it's own when you need more tha 4 GB of ram. Hence they absolutely rock as database and application servers.

Added by gjw:

on fbsd6, minor additions.. in httpd.conf, also make sure that mod_proxy, mod_proxy_connect, and mod_proxy_http are uncommented in the loadmodule section, and at the very end of the file (beginning of the VirtualHost? settings), uncomment NameVirtualHost? *:80 and make sure you have an initial virtual host set up to take all other requests (see http://httpd.apache.org/docs/2.0/vhosts/name-based.html)... unless you're doing IPbased vhosting, in which case, see same document.

Response from Ken on 11/25/2005-- Yes, that goes w/o saying... I was making assumption that anyone doing vhosting had the Apache basics. Correct me if I'm wrong but I don't think you need mod_proxy_connect though unless you're proxying ssl connects.

wac 12/2/2005-- I wrote up instructions for running Typo (or any other Rails app) with Apache httpd with MPM worker and FastCGI. Performance has been really good. http://www.carrel.org/articles/2005/11/21/rails-with-apache-mpm-worker