Wednesday, September 16, 2009

Serving Rails sites using Passenger with SSL

This comes up a lot in the rails community, and it's not too difficult to do.

This brief guide assumes that you have a working site hosted on a Linux server using Passenger (and also having installed mod_ssl for Apache) and that you want to secure the whole thing using SSL. If not, you'll need to add extra steps like deploying the project, installing the gems, running your migrations, installing mod_ssl and Passenger for Apache etc.

We will generate our own (horrible...) temporary SSL cert assuming that later we will get a real one from a company like VeriSign and can swap out the temp one. Also, because the staging environment I've been deploying to runs on XAMPP and we've still needed phpMyAdmin while developing the site, I'll throw in the "Serving a PHP folder from passenger" whirlwind tour (for a much better explanation, see AbleTech's description).

Firstly Passenger will want a log file it can write to.
You'll want your Passenger logs to come into a log file in your rails site's log folder (near the end of the post you'll see I've configured "ErrorLog /opt/railssite/log/apache.log" in my httpd.conf). Create one in your rails apps log directory and run "chmod 0666" against it so that Apache will have read/write rights. If you don't, Apache will write all of the Passenger logging to the standard error_log and you'll waste time trying to see why your site isn't working like you thought it should.

Second create your SSL key/crt files.
Create a folder to hold your ssl key/crt files. We're going to use openssl to generate our key and certs so if your flavor of Linux doesn't have it installed you'll need to use apt or yum or something to get it. Here's the brief rundown on how to generate a valid key/cert with openssl, with the password "password". You may need to do this as root.

Run "openssl genrsa -des3 -out 1024"
When prompted enter the passphrase "password"
This generates the file Because you want to get into good security habits early run "chmod 400" against this file, this makes sure that your key file is only editable by the root user.

To generate the self signed cert from your key run "openssl req -new -key -x509 -out"
This generates the file which is the certificate that we're going to use to secure this site.

Thirdly configure Apaches httpd.conf.

We're going to forward all traffic that comes in on port 80 to port 443 by configuring a RewriteRule in a virtual host listening on port 80 to forward all requests to the virtual host listening on port 443. You don't have to do this but users are easy to frustrate. Since HTTP is roughly equivalent to HTTPS in the mind of the average end user while they're typing a URL, making both "just work" is easy and highly recommended.

For the purposes of the folowing httpd.conf snippet, my SSL key/crt files were located in /opt/lampp/ssl and my rails site was in /opt/railssite

<VirtualHost *:80>
RewriteEngine On
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=permanent]

<IfDefine SSL>
<VirtualHost *:443>
RailsEnv staging
DocumentRoot /opt/railssite/public
ErrorLog /opt/railssite/log/apache.log
SSLEngine on
SSLCertificateFile /opt/lampp/ssl/
SSLCertificateKeyFile /opt/lampp/ssl/
SSLProtocol all -SSLv2
SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown

<Location /phpmyadmin>
PassengerEnabled off

A few notes on the above.

"PassengerEnabled off" in the location phpmyadmin ensures that when someone requests Passenger doesn't try to serve it up and PHP takes over. If Apache can't make the jump and find the directory you need it to serve up on it's own, add "Alias /phpmyadmin /dir/to/phpmyadmin" above the directory node to force it to look in the right physical location. This is particularly useful if you need to serve up something like a PHP blog alongside your rails application, and want to keep the source code for this in the rails project.

Often you see people configure the rails document root in both virtualhosts, it's not necessary and depending on how your copy of Apache is running, it can cause Passenger to spin up a new rails instance just to have the user forwarded to the secured site. On a heavy usage site this could cause a lot of memory to be consumed for no reason.

It goes without saying that the secured virtualhost (443) should be configured within an <IfDefine SSL> node. This ensures that the site isn't served up unsecured if SSL fails for some reason.