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
- Tiger (OSX 10.4)
- XCode 2.0 or newer and developer’s tools
- 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!
Zigadenus venonosus var. venonosus (Liliaceae); Meadow death-camas

Hi,
Great tutorial, it’s exactly what I’ve been looking for.
It seems I can get everything working except for being able to access the user folders, by this I mean http://localhost:3500/~Mic/ … which just gives a 404 error.
All other requests work, including PHP on the standard doc root set in server.document-root … just not when I try to get to the users folder.
Any ideas !?
I have bound the server to port 3500 just because I wanted to test against Apache, I will swop over eventually, but need to get this working first.
Thanks
Mic
Mic,
What is the exact code you used in lighttpd_shared.conf? To access http://yourserver/~Mic/ it should look like this:
userdir.basepath = "/Users/" userdir.path = "Sites" userdir.include-user = ("Mic") # only allow requests for this user (optional)The third line is optional, and allows you to whitelist users. You can include as many users as you want, each with their own userdir.include-user statement. If you omit all of those statements, then access is allowed for all users.
The other possibility is that you don’t have a /Users/Mic/Sites/index.html file, and so even though lighty is looking in the right place there is nothing for it to serve.
Binding to port 3500 shouldn’t make a difference, as long as the rest of the configuration is the same, so that’s not the problem.
Does this help?
Hi Brent,
Thanks for that mate, you sorted it.
For some reason I assumed userdir.include-user = (”username”) was some sort of internal variable which picked up the current username. Doh !!
All working now
Once again, fantastic tutorial mate … I just need to figure out how to compile in the GD part of PHP and I’ve got an exact copy of my Apache setup, but instead using Lighttpd and including Rails, 5 star … great stuff.
Thanks again.
Mic
Thanks for your tutorial! It’s what I was looking since many many days ago.
Lighttpd rocks! I love it
Thanks for the great sample config file! The best I found on Google…
One thing about launchd: You probably have to run the command “sudo launchctl load /Library/LaunchDaemons/net.lighttpd.plist” for launchd to recognize the new plist file, though.
Ulf: I don’t remember having to run launchtcl; I think that I rebooted my box, which did the trick. However, it was so long ago, that I can’t really say.
In any case, you’re welcome for the config file; I’m glad it’s helpful to someone other than me.
Thanks for the instructions. This was a big help in getting me up and running. Mongrel has some good info on Lighttpd/Mongrel deployment as well at: http://mongrel.rubyforge.org/docs/lighttpd.html
Hi.
Good design, who make it?
Naisi: It’s my own. :+)
Thanks for this very concise primer, just what I was searching for. Made it very easy to set this up on 10.4.11
A note:
The “Program” key seems to be at least unnecessary - in my version launchd even refuses to run the file on startup if it’s present.
I noticed you cannot chroot/chown a process if you start it with launchd - you have to set the owner in the plist file.
You can start/stop lighttpd with launchd too: just type
> launchctl load /Library/LaunchDaemons/
to load the new plist files you put there, then start lighttpd with
> launchctl start net.lighttpd
stop it with
> launchctl stop net.lighttpd
same works with net.lighttpd_ssl of course
for reference here’s my plist file minus the header:
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.lighttpd_ssl</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/sbin/lighttpd</string>
<string>-f/etc/lighttpd/lighttpd_ssl.conf</string>
<string>-D</string>
</array>
<key>UserName</key>
<string>www</string>
<key>GroupName</key>
<string>www</string>
</dict>
</plist>
PS: “man launchd.plist” gives you all the keys that are possible to use in a plist that goes into one of the Lauch* folders