HOWTO: Lighttpd with SSL, Rails, PHP and MySQL on OSX 10.4 (Tiger)

I’ve run Apache on my Mac for as long as I’ve had OSX, using either the built-in version or the ServerLogistics package, which they don’t make anymore. It always worked great, and when I started learning PHP, it was easy to install and make work. Getting SSL to work was a little bit more of a challenge, but once I found the right instructions it was a breeze.

But now I need to serve Rails as well. Getting Apache to do RoR with fcgi can be a hassle, and it’s apparently slow as well, so I decided to switch to lighttpd, or lighty as it’s called.

For development purposes, using the standard script/server is a no-brainer; however I want a “production” environment as well. One that can serve Rails, and legacy PHP, and do SSL for authentication, while talking to MySQL. I discovered that lighty can do all of these things, but how?

I looked around, and once you get to the deployment phase of RoR, there isn’t a lot of good information on the web, especially for smaller set-ups like mine; essentially, I’m almost the only person who hits my web server, as it houses some personal productivity stuff just for me that most other people don’t find interesting. That and my photo collection for friends to browse.

So I looked and looked, and found a lot of disjointed bits and bobs, but I didn’t find a good how-to for non-unix geeks. But, given the ease of use of lighty, I’ve put one together. Enjoy!

Requirements

  1. Tiger (OSX 10.4)
  2. XCode 2.0 or newer and developer’s tools
  3. Patience and a nice snack while waiting for compiling to happen

Building Ruby, Gems, RoR, FastCGI, PCRE

Go to Dan Benjamin’s excellent instructions to do all of these things, and follow them well. However, stop when you get to the lighttpd instructions, because we’re going to make a change. Do this instead of what he says:

curl -O http://lighttpd.net/download/lighttpd-1.4.11.tar.gz
tar xzvf lighttpd-1.4.11.tar.gz
cd lighttpd-1.4.11
./configure --prefix=/usr/local --with-pcre=/usr/local --with-openssl
make
sudo make install
cd ..

The only difference is the addition of –with-openssl to the configuration, which will compile lighttpd with SSL (v2 and v3) support.

Building MySQL

You can choose to build MySQL according to the HiveLogic instructions, or you can use the MySQL 5.0 packages from the dolphin’s mouth, so to speak, and avoid the compile time. This works for version 4.1 as well. I’ve tried both versions with the setup described here, and both work.

Don’t forget to install the MySQL native bindings (again, from the HiveLogic instructions) as well.

But, I’ve already got this stuff working!

Chances are, you’ve already got a full setup that works, because you saw the HiveLogic instructions long before I ever wrote this page. If that’s the case, then all you need to do is follow my lighttpd compile instructions, which will over-write the pre-existing lighttpd version, with no ill effects.

Building PHP

First, download the PHP 5.1.2 full source code, and unpack the tarball by double-clicking it in the finder. I would recommend moving the unpacked “php-5.1.2″ folder to the same folder where you downloaded all of the source code from the hivelogic instructions.

Second, open a terminal, and execute the following commands:

./configure --enable-fastcgi --enable-discard-path --enable-force-redirect --with-zlib --with-xml --with-mysql=/usr/local/mysql --prefix=/usr/local/php5-fcgi --disable-cli --enable-memory-limit --with-layout=GNU --with-regex=php
make
sudo make install

When this is done, you’ll have a working cgi-fcgi version of PHP. We’ll hook this up to lighttpd in a minute, when we get to the lighty configuration files.

SSL

Before we start configuring lighty, we’ve got to get an SSL certificate. If you’re creating your own, you can follow the instructions from the lighty web site:

openssl req -new -x509 -keyout host.pem -out host.pem -days 365 -nodes

If you already have a certificate that’s a .crt and a .key file, you have to make them snuggle up into a single .pem file:

cat host.key host.crt > host.pem

Both of these methods result in a single .pem file, usually named “host.pem” where “host” is the name of the server you’re using the certificate for. This file can be stored anywhere you want on your system, since the lighty configuration takes an explicit path to it.

Configuring lighttpd

Now the more difficult part: we get to configure lighty. I’ll present the config files one chunk at a time, explaining what the various commands do. I’ve chosen to store mine in /etc/lighttpd/ to mimic how Apache stores its files (/etc/httpd/). Any path will do, just change the references to that directory in the coming intsructions.

First, we’ll set up 2 top-level files, which are both quite simple. They set up two nearly identical configurations for the lighty daemon, that run side-by-side. The first scans port 80, and serves http requests. Let’s call this file lighttpd.conf.

include "lighttpd_shared.conf"

server.port = 80

server.errorlog    = "/etc/lighttpd/lighttpd.error.log"
accesslog.filename = "/etc/lighttpd/lighttpd.access.log"

The first line causes lighty to parse the main configuration file, which we’ll go through below. The next line binds lighty to port 80, the default for http, and then we assign error log files.

We do something very similar for the other top-level file, the one that configures the SSL daemon. I’ve called mine lighttpd_ssl.conf.

include "lighttpd_shared.conf"

server.port = 443

server.errorlog    = "/etc/lighttpd/lighttpd_ssl.error.log"
accesslog.filename = "/etc/lighttpd/lighttpd_ssl.access.log"

ssl.engine         = "enable"
ssl.pemfile        = "/etc/lighttpd/host.pem"

In this case, we’ve bound the daemon to port 443, the default for secure connections. We’ve specified log files, and then enabled the SSL engine and specified the path to the .pem file.

By the way, the log files must exist before you run the daemon, and have the right permissions. To do this, simply touch the files, assign them to “www” and set the permissions:

cd /etc/lighttpd
touch lighttpd.error.log
touch lighttpd.access.log
touch lighttpd_ssl.error.log
touch lighttpd_ssl.access.log
sudo chown www *.log
sudo chmod 0666 *log

Now, on to the meat of the matter. Both of our top-level files parse the lighttpd_shared.conf file, so that both daemons run in parallel and do the same things. This means that all pages in our web server can be served with a regular or a secure connection. Here’s the meat:

server.modules = ( "mod_rewrite",
                   "mod_access",
                   "mod_fastcgi",
                   "mod_userdir",
                   "mod_accesslog" )

# Main folder containing web documents
server.document-root = "path/to/main/"

# Allow http://www.domain.com/~username/ style requests
userdir.basepath = "/Users/"
userdir.path = "Sites"
userdir.include-user = ("username") # only allow requests for this user (optional)

# If no file is specified, what to look for?
index-file.names = ( "index.html", "index.htm", "index.php" )

# Required event handler for OS X
server.event-handler = "freebsd-kqueue"

# Run the server under the user-name "www" for security purposes
# To bind to port 80, the server must be called by root user, but we don't want
# the server to have free run of the box, so it runs as "www"
server.username = "www"
server.groupname = "www"

# Set up the appropriate MIME type mappings
mimetype.assign             = (
  ".pdf"          =>      "application/pdf",
  ".sig"          =>      "application/pgp-signature",
  ".spl"          =>      "application/futuresplash",
  ".class"        =>      "application/octet-stream",
  ".ps"           =>      "application/postscript",
  ".torrent"      =>      "application/x-bittorrent",
  ".dvi"          =>      "application/x-dvi",
  ".gz"           =>      "application/x-gzip",
  ".pac"          =>      "application/x-ns-proxy-autoconfig",
  ".swf"          =>      "application/x-shockwave-flash",
  ".tar.gz"       =>      "application/x-tgz",
  ".tgz"          =>      "application/x-tgz",
  ".tar"          =>      "application/x-tar",
  ".zip"          =>      "application/zip",
  ".mp3"          =>      "audio/mpeg",
  ".m3u"          =>      "audio/x-mpegurl",
  ".wma"          =>      "audio/x-ms-wma",
  ".wax"          =>      "audio/x-ms-wax",
  ".ogg"          =>      "application/ogg",
  ".wav"          =>      "audio/x-wav",
  ".gif"          =>      "image/gif",
  ".jpg"          =>      "image/jpeg",
  ".jpeg"         =>      "image/jpeg",
  ".png"          =>      "image/png",
  ".xbm"          =>      "image/x-xbitmap",
  ".xpm"          =>      "image/x-xpixmap",
  ".xwd"          =>      "image/x-xwindowdump",
  ".css"          =>      "text/css",
  ".html"         =>      "text/html",
  ".htm"          =>      "text/html",
  ".js"           =>      "text/javascript",
  ".asc"          =>      "text/plain",
  ".c"            =>      "text/plain",
  ".cpp"          =>      "text/plain",
  ".log"          =>      "text/plain",
  ".conf"         =>      "text/plain",
  ".text"         =>      "text/plain",
  ".txt"          =>      "text/plain",
  ".dtd"          =>      "text/xml",
  ".xml"          =>      "text/xml",
  ".mpeg"         =>      "video/mpeg",
  ".mpg"          =>      "video/mpeg",
  ".mov"          =>      "video/quicktime",
  ".qt"           =>      "video/quicktime",
  ".avi"          =>      "video/x-msvideo",
  ".asf"          =>      "video/x-ms-asf",
  ".asx"          =>      "video/x-ms-asf",
  ".wmv"          =>      "video/x-ms-wmv",
  ".bz2"          =>      "application/x-bzip",
  ".tbz"          =>      "application/x-bzip-compressed-tar",
  ".tar.bz2"      =>      "application/x-bzip-compressed-tar"
 )

# Don't server these files statically, for security
static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc", ".pl", ".yml" )

# Use mod_access to deny direct access to files ending in ~ or .inc; these are usually
# code or backup files
url.access-deny             = ( "~", ".inc" )

# Disable auto-generated directory listings, for security
dir-listing.activate        = "disable"

# Disable range requests for PDF files
$HTTP["url"] =~ “\.pdf$” {
  server.range-requests = “disable”
}

# Set up a fastcgi server for PHP
fastcgi.server = ( “.php” => ((
                     “bin-path” => “/usr/local/php5-fcgi/bin/php”, # path to php binary
                     “socket” => “/tmp/php.socket”,
                 )))

# Now create virtual subdomains for each rails app we want; myrailsapp.domain.com
# Repeat the following block for each rails app, and for each one
# replace “myrailsapp” with the name of your application
# This sets up separate fcgi processes for each application

$HTTP["host"] =~ “^myrailsapp\.” {
     var.myrailsapp = “/path/to/rails/app”
     server.document-root = var. myrailsapp + “/public”
     server.error-handler-404 = “/dispatch.fcgi”
     fastcgi.server = ( “.fcgi” =>
                     ( “localhost” =>
                         ( “bin-environment” => (”RAILS_ENV” => “production”),
                           “bin-path” => var. myrailsapp + “/public/dispatch.fcgi”,
                            “socket” => “/tmp/myrailsapp.fcgi.socket”
                         )
                      )
                 )
}

You can tweak the fastcgi performance by using the lighttpd instructions for directives such as “min-procs” and “max-procs” but that’s up to you.

Lighty at startup

Wouldn’t it be nice if lighty launched at startup, and we didn’t have to worry about it? How about if it relaunched whenever it crashed? That would be even better. Save the following to /Library/LaunchDaemons/net.lighttpd.plist and watch the magic happen, thanks to froehle:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>net.lighttpd</string>
	<key>OnDemand</key>
	<false />
	<key>Program</key>
	<string>/usr/local/sbin/lighttpd</string>
	<key>ProgramArguments</key>
	<array>
		<string>/usr/local/sbin/lighttpd</string>
		<string>-f/path/to/lighttpd.conf</string>
		<string>-D</string>
	</array>
</dict>
</plist>

Oh, wait, before the magic, there’s one more (/Library/LaunchDaemons/net.lighttpd_ssl.plist):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>net.lighttpd_ssl</string>
	<key>OnDemand</key>
	<false />
	<key>Program</key>
	<string>/usr/local/sbin/lighttpd</string>
	<key>ProgramArguments</key>
	<array>
		<string>/usr/local/sbin/lighttpd</string>
		<string>-f/path/to/lighttpd_ssl.conf</string>
		<string>-D</string>
	</array>
</dict>
</plist>

Of course, if you’ve installed the lighttpd binary someplace other than /usr/local/sbin then you should change those 2 lines in each .plist file, and you should specify the path to your .conf files properly.

Graceful restarts

Wouldn’t it be super-nice if we could issue graceful restart commands? It’s actually very easy in Tiger (10.4) since launchd monitors apps it has started, and re-opens them if they’ve quit or crashed. That means that all we have to do is cause a graceful shutdown, and launchd will take care of the restart for us. Put the following in /usr/local/sbin/ in a file called “lighttpdctl” (named after apachectl) with the appropriate path to your config files:

#!/bin/sh
sudo killall -9 lighttpd

Make it executable by doing this:

chmod u+x /usr/local/sbin/lighttpdctl

Now, from anywhere (since /usr/local/sbin is in our path), you can call lighttpdctl to do a graceful restart. Note, if you’re running a port 3000-bound script/server development instance of lighty, it will also quit.

We’re done

Well, almost. You now have to restart your machine to have launchd do its thing. If you issue the kill command to lighttpd and the configuration is bad (for whatever reason), and lighty bails on startup, launchd will no longer monitor the process. This means that when you’re mucking around, there’s a lot of sudo lighttpd -f/etc/lighttpd/lighttpd.conf -D from the prompt until you’ve got it working, followed by a restart. I’m pretty sure that there’s a better way to do the config debugging, but I’m not 1337 enough with FreeBSD and launchd-speak to do it any better. If somebody out there knows, please post it in the comments.

Enjoy!

11 Responses to “HOWTO: Lighttpd with SSL, Rails, PHP and MySQL on OSX 10.4 (Tiger)”

Subscribe to comments

Leave a Reply