metz·log

Running Trac on Nginx with Phusion Passenger

·

I'm currently investigating ways to replace Apache httpd with Nginx on srcbox.net. One of the services that runs on the webserver is Trac for hosting a wiki-based website and bug tracking for software projects. Because I'm already using Phusion Passenger for hosting Ruby on Rails projects, I decided to also give it a try to host WSGI applications together with Nginx.

Please note that this post is quite similar to the one written by Graham Edgecombe about the same subject. I found a few things missing and notable so I decided to do my own little writeup on what I had to do to get things working.

Trac Setup

As the actual Trac setup is already present on my server and I just wanted to replicate a similar setup in my testing VM, I setup Trac like this:

  • Installed package trac, I used version 0.12.x which is available via Debian Backports
  • Added a system user that owns all trac project files and is used to run the WSGI webserver processes:
    adduser --system --home /var/lib/trac --group --disabled-password --disabled-login trac
    
  • Initialized a trac environment below trac home:
    trac-admin /var/lib/trac/project initenv
    
  • Deployed the webserver files using:
    trac-admin /var/lib/trac/project deploy /var/www/trac/
    
  • Added symlinks to make Phusion Passenger accept Trac as a WSGI application:
    ln -s /var/www/trac/htdocs /var/www/trac/public
    ln -s /var/www/trac/cgi-bin/trac.wsgi /var/www/trac/passenger_wsgi.py
    

Installing Nginx

Nginx installation is quite a bit of work if you're used to a single aptitude install package, all features/modules have to be enabled/added at build-time which means rebuilding Nginx if you want to use Phusion Passenger. Because I plan to move authentication to a central place soon I also added Nginx HTTP Auth PAM to the mix. Furthermore I'm also tinkering with the idea to give owncloud a spin so WebDAV support is probably helpful too.

The installation basically went like this (rough steps, this is not a place for step-by-step tutorials):

  • Downloaded latest stable Nginx release
  • Downloaded latest ngx_http_auth_pam_module release
  • Unpacked both in a convenient place
  • Installed Phusion Passenger via rubygems (I used ruby and gem from ruby1.9.1 in Debian Squeeze)
  • Ran passenger-install-nginx-module
  • Chose 2. No: I want to customize my Nginx installation.
  • Told the installer where to find unpacked Nginx sources
  • Added additional configure arguments. For Auth PAM and optionally needed WebDAV support I used:
    --add-module=/path/to/unpacked/ngx_http_auth_pam_module --with-http_dav_module
    
  • Grabbed an Espresso until the build was finished (didn't really take long)

On Debian it's probably helpful to install a fake webserver package to satisfy dependencies for other packages that depend on http-server. I used equivs to generate myself an nginx-dummy package.

Configuring Nginx

For starting up Nginx I simply grabbed the init-script from the Debian source package of nginx, , put it into /etc/init.d/, adapted the paths to my Nginx install in /opt/nginx and installed it in the default runlevels using insserv nginx.

Adding a virtual host to Nginx that serves Ruby on Rails or a WSGI application is usually very easy, for Trac however a tiny bit more care had to be taken...

Pitfall 1: HTTP Auth

With Trac there's one catch: HTTP Authentication. It took me quite some time to find out why HTTP-Auth worked fine according to the Nginx access.log but Trac still thought I wasn't logged in: Nginx needs to forward the user name to the WSGI application. For Phusion Passenger this can be done using the following line:

passenger_set_cgi_param REMOTE_USER $remote_user;

Pitfall 2: Location Blocks

Nginx uses location blocks for different behavior depending on the url. For Trac the /login path should trigger HTTP authentication. Together with Phusion Passenger all I got after successful login however was a simple "404 Not Found" page.

It turned out that for location blocks one has to enable Phusion Passenger again, a prior passenger_enabled on; line in the parent block does not apply to the inner location block.

Final Nginx Config

The complete host configuration for my test setup that contains all needed things to avoid the above pitfalls looks like this:

server {
    listen                  80;
    server_name             trac.domain.lan;
    root                    /var/www/trac/public;
    passenger_enabled       on;
    # Forward HTTP-Auth user to WSGI
    passenger_set_cgi_param REMOTE_USER $remote_user;

    location /login {
        auth_pam                "Trac on domain.lan - Restricted Access";
        # Use the same PAM service as dovecot for now
        auth_pam_service_name   "dovecot";
        # Reenable Passenger to get a redirect after login instead of a 404
        passenger_enabled       on;
        # Forward HTTP-Auth user to WSGI
        passenger_set_cgi_param REMOTE_USER $remote_user;
    }
}

What next?

This setup is probably still far from complete. Most notable things that I did not setup or test yet include:

  • Paths for a manual Nginx install do not obey the FHS at all, logs and temporary files end up on the wrong partition which is a bad idea security wise.
  • Log rotation is missing. I can probably grab parts from the Debian Nginx package again.
  • Serve multiple projects/environments with the above webserver setup
  • Replace the rather ugly HTTP Authentication with Trac Account Manager plugin
  • Use TLS and enforce login to happen encrypted only

Of course Trac is by far not the only web-application, I'm probably going to document a few other things I have installed as part of my new Nginx test installation :)