Multi Host Trac using NGINX

Sunday, 2010-01-10 02:52, 1263091927 seconds since Unix epoch

So, after using NGINX as my primary web server for over six months, I’m quite happy with it. The sites I’ve migrated have all been running without real practical problems what so ever. During my usage of NGINX, one of the most useful aspects is it’s use of assignable variables in the configuration files. Where I needed to write the same twenty-something lines of configuration for every similar virtual host using Apache, NGINX allows me to replace all that with a single write-once works-for-all virtual host. Trac is one of the things for which this will come in handy. I’m hosting a dozen of Trac sites, all requiring their own Apache Location directive. I’ve replaced all of that with a few lines of NGINX config and a single init-script. I’m going to show you how.

Before we start, I have to tell you I’ve only tested this using Trac 0.11.6 and NGINX 0.7.64 on Debian GNU/Linux. It’ll probably work everywhere, except on Windows. Trac’s FastCGI simply won’t run.

Just like NGINX hasn’t got mod_php, mod_python is equally absent. Which is a good thingtm. Trac supports running every site in FastCGI mode since 0.9, making it entirely NGINX-compatible. There’s even a config sample on the wiki, which we are not going to use.

After creating your Trac environment using trac-admin, you’ll have to deploy the site first. Since I’m using Debian, it’s going to end up somewhere under /var/www/. I’ve also put my Trac sites in /var/trac/, just to make things a little more complicated. Put your files wherever you fancy, I’ll keep using these paths. Say, we’re about to host helloworld’s project trac page.

trac-admin /var/trac/helloworld initenv
mkdir -p /var/www/trac && chown www-data:www-data /var/www/trac
trac-admin /var/trac/helloworld deploy /var/www/trac/helloworld
chown -R www-data:www-data /var/www/trac/helloworld /var/trac/helloworld

Replace www-data with whatever user your NGINX is using. This will initialize your Trac environment and deploy the site-specific static files to the webroot. This will also provide you with the FastCGI server, which in turn can be started using Lighty’s spawn-fcgi. As always, I’ll supply the init script you can use to automate this. This time though, I’ve simplified things a bit. Since all of the Trac FastCGI processes are the same anyway, we can use symlinks and the init script’s basename to unify our configuration. The only thing you have to do to start the site’s FastCGI daemon during the system boot is to copy the fcgi-trac-base script to /etc/init.d/, and the following.

ln -s /etc/init.d/fcgi-trac-base /etc/init.d/fcgi-trac-helloworld
update-rc.d fcgi-trac-helloworld defaults
/etc/init.d/fcgi-trac-helloworld start

Now we’ve got Trac itself running, it’s time to get NGINX to actually serve the site. I’ve chosen, because of SSL limitations, to host Trac sites under https://www.domain.tld/trac/project instead of a sub domain. I think the config’s easy enough to change this behavior. The first thing we want to do is to make sure static content, like CSS and images, is served directly by NGINX instead of tunneled through FastCGI. All the following configuration goes, in order, into your domain’s server { } block.

location ~ ^/trac/([0-9a-zA-Z\-_]*)/chrome(.*)$ {
    alias /var/www/trac/$1/htdocs$2;
}

All of the static content will now be caught before Trac’s even touched. This increases the speed of serving this content drastically. Next up, calling Trac itself. It becomes a little tricky from here, since we’re juggling with regular expressions and variables. Once you’ve set them up correctly though, you shouldn’t have to edit a single line anymore when adding extra Trac sites. First, we want to store which Trac site we’re accessing. In this case, we want the helloworld part from our Trac URI.

if ($uri ~ ^/trac/([0-9a-zA-Z\-_]*).*$) {
    set $trac_host $1;
}

We’ll make good use of this variable. The next step is to call the right Trac FastCGI server. Because we’ve used a self-configuring init script, we can safely assume the location of the listening UNIX socket. We can even use this variable to point to the right authentication file, if you wish to secure your Trac sites. The right way to create this authentication file is using Apache’s htpasswd.

mkdir /etc/nginx/trac
htpasswd /etc/nginx/trac/trac.helloworld.passwd johndoe
chmod 400 /etc/nginx/trac/trac.helloworld.passwd
chown www-data /etc/nginx/trac/trac.helloworld.passwd

I’ll explain the following block in parts, because there are quite some gotcha’s hidden between the lines.

location ~ ^/trac {
    auth_basic            "Trac";
    auth_basic_user_file  /etc/nginx/trac/trac.$trac_host.passwd;

I’ve used a regular expression match in the location instead of a regular location definer because otherwise the PHP interpreter out of my previous NGINX post would try to parse anything that ends with .php, including PHP files in Trac’s browse source functionality. It would fail of course, but Trac will also fail to produce the pretty syntax highlighted source code. You do have to make sure Trac’s configuration comes before PHP’s.

    fastcgi_split_path_info ^(/trac/[0-9a-zA-Z\-_]*[/]*)(.*)$;

It took me a while, and some help, to figure this out. To get the right PATH_INFO FastCGI variable, you can’t just use the regular expression in the previous if-statement. It will work, except for URIs with urlencoded characters in them, like spaces. NGINX keeps these %something characters, while FastCGI’s PATH_INFO expects these strings to be supplied decoded. The special function fastcgi_split_path_info corrects this error, and will supply you with a correct value stored in the $fastcgi_path_info variable. You’ll have to be using NGINX 0.7.31 or later to get this to work.

    fastcgi_pass   unix:/var/run/trac-fastcgi-$trac_host.sock;

Now we can pass everything to our eagerly waiting UNIX socket, which has been set up by the fcgi-trac-base init script. As you can see, it’s important the project name throughout the config matches exactly. Otherwise, some components might fail to find the right locations.

    fastcgi_param  HTTPS            on;
    fastcgi_param  QUERY_STRING     $query_string;
    fastcgi_param  CONTENT_TYPE     $content_type;
    fastcgi_param  CONTENT_LENGTH   $content_length;
    fastcgi_param  SCRIPT_NAME      /trac/$trac_host;
    fastcgi_param  PATH_INFO        /$fastcgi_path_info;
    fastcgi_param  AUTH_USER        $remote_user;
    fastcgi_param  REMOTE_USER      $remote_user;
    fastcgi_param  REQUEST_METHOD   $request_method;
    fastcgi_param  SERVER_NAME      $server_name;
    fastcgi_param  SERVER_PORT      $server_port;
    fastcgi_param  SERVER_PROTOCOL  $server_protocol;
}

Finally, we can add the right variables to get FastCGI the information needed to serve the Trac pages. Remove the HTTPS variable if you don’t use HTTPS for your Trac sites. Also, remove the *_USER variables if you don’t use NGINX’s HTTP authentication for Trac.

Now you can easily add new Trac sites by following the next steps.

trac-admin /var/trac/$PROJECT initenv
trac-admin /var/trac/$PROJECT deploy /var/www/trac/$PROJECT
chown -R www-data:www-data /var/trac/$PROJECT /var/www/trac/$PROJECT
htpasswd /etc/nginx/trac/trac.$PROJECT.passwd $USER
chmod 400 /etc/nginx/trac/trac.$PROJECT.passwd
chown www-data /etc/nginx/trac/trac.$PROJECT.passwd
ln -s /etc/init.d/fcgi-trac-base /etc/init.d/fcgi-trac-$PROJECT
update-rc.d fcgi-trac-$PROJECT defaults
/etc/init.d/fcgi-trac-$PROJECT start

I’ve combined all of the config into a single file you can place in your NGINX config directory, which you can include inside any server {} block.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>