Ruby on Rails
SCGI Rails Runner with Apache 2 on Linux (Version #73)

This is a quick guide to getting Zed Shaw’s SCGI Rails Runner running with Apache2.

Download and install mod_scgi.


# wget <a href="http://quixote.python.ca/scgi.dev/apache2/mod_scgi.c">http://quixote.python.ca/scgi.dev/apache2/mod_scgi.c</a>
# /usr/local/apache2/bin/apxs -i -c mod_scgi.c # others have found "make; make install" to work, as well.

In httpd.conf set up mod_scgi:

LoadModule scgi_module modules/mod_scgi.so

In httpd.conf, inside your <VirtualHost> directive add in the mount point, allow the .htaccess file to have control, and instruct it not to serves files with “.” in them from RoR:


# Tell it to let scgi serve all files [rather large scope, but hey :) ]
SCGIMount / 127.0.0.1:9999
#Tell it not to let the scgi serve non RoR files (we assume files with a dot in them, such as picture.jpg, should be served by apache, not RoR).
    <LocationMatch \..+$>
        # don't handle those with SCGI
        SCGIHandler Off
    </LocationMatch>

Then it’s just a case of running the scgi server:

# cd <railsapp>
# scgi_ctrl config
<enter password you want -- this password is prompted when you restart the server, etc.>
# scgi_ctrl start

Done! (Note that your RoR app will be running in production mode.)

Another way to set it up would be to add the following to inside a virtualhost directive (same effect, slightly more complex).


<Directory /path/to/rails/public/>
        # allow .htaccess files to take effect
        AllowOverride All

  1. Tell it to let scgi serve files called “scgi-bin/whatever”
    SCGIMount /scgi-bin/ 127.0.0.1:9999
    #Tell it not to let the scgi serve non ror files (they probably have a dot in them, so instruct it not to serve them).
    <LocationMatch \..+$>
    # don’t handle those with SCGI
    SCGIHandler Off

and add this rewrite-rule thus: in public/.htaccess, replace the dispatch.(f)cgi line with:

RewriteRule ^(.*)$ /scgi-bin/%{REQUEST_URI} [QSA,L]</pre>
. This

Same effect, except you could use that .htaccess to perhaps add more flexibility for static files, later.

Read the docs for more information on changing to production mode [hint — it defaults to production] and for more scgi_rails options.

Hint
If you want to run multiple apps on different virtual hosts, start the scgi_rails on different ports when configuring scgi_rails (default port is 9999). Then specify the port in your virtual host config in the SCGIMount directive.

More ways to serve static files — Accessing stylesheets, images, javascripts while mounting /

You will also note that some files are instructed not to be served by the scgi handler (static files).

The following setup accomplishes this in a different way than just filtering for “any file with a .” You set a default route for / and don’t have to modify the .htaccess file – dead simple.


<VirtualHost 123.456.789.123>
  ServerName <a href="http://www.domain.com">www.domain.com</a>

  DocumentRoot /hosting/domain.com/public/
  SCGIMount / 127.0.0.1:10000

  <LocationMatch "/(stylesheets|images|javascripts).*$"> 
    SCGIHandler Off
  </LocationMatch>
</VirtualHost>

Also this site explains a way of serving all files with a . in them as static. The bottom of this file also has another example. The InstantRails guys do it thus

Non-root source (i.e., not SCGIMount /, or, how to make http://hostname/subdir point to an RoR app)

Because


Alias /blender "/home/rpack/blender"
SCGIMount /blender/ 127.0.0.1:9999  # note that we can't do this as we already have 'something' applied to blender.  Try it another way

Is not possible (it has two directives for a directory—apache gets confused), you need to do some conniving if you want a host a rails app in a directory.
This page
deals with it, as also the instantrails page on the same.

Some user experiences:
—————————
I am just another guy, and I got it running in a rather complex configuration with Apache2.2. I am using an approach that I have not yet read on the web, so I think I should share it.
I am using FreeBSD 5.5 (this should not matter much) and three different systems for database, apache and SCGIservers (isolated by firewalls). But this approach should run as good on a single server.

I use multiple Rails Applications within the same VirtualHosts, some with and some without SSL (and some must behave differently if accessed with or without SSL).

The RailsApps are in arbitrary places of the filesystem, not within the Apache DocumentRoot.
From the Web my applications are accessed as http://myhost.org/railsapp1, http://myhost.org/railsapp2, etc.

If you want only one application per VirtualHost, it might be not a bad idea to do it the same way and then redirect your RootURL to the actual URL – this could pay off when extensions or upgrades are needed.

How to do:

  • Compile and install the mod_scgi (here it is 1.11) on the Webserver and LoadModule in the httpd.conf.
  • Install scgi_rails GEM (and two prerequisite GEMs) on the SCGIserver.
  • Run _scgi_ctrl config_ in the main dir (nowhere else!) of your rails app, and enter a password for (possibly remote) admin access to the SCGI server.
  • this has created a scgi config file in config/scgi.yaml. There are two ports mentioned, one is where the Webserver calls the SCGIServer for productive action, and the other (the “druby” URL) is the SCGIServer admin access port. You can change them if necessary. (security issue: these ports should not be visible from outside your server/site. Especially the admin port is only secured by a telnet-style plaintext-transmitted password. The admin port can be switched off with an extra option.)
  • If you place your logfile somewhere else than the default location, edit the created entry starting with :logfile:, and then add another line starting with :log_file: and the same location. (This is to workaround a misspelling in the scgi_rails code.)
  • Choose a suitable unpriviledged user to run the SCGI-server. Take care that the logfile and the rails-sessionstore and all the ressources your app needs are accessible by that user.

Now for the special thing: We need static content from the RailsApp public directories, and we need to route application accesses to SCGI. SCGI does the latter with SCGIMount, which takes the respective Location out of the apache namespace and moves it to SCGI, so the static content (stylesheets, etc) gets invisible to the webserver.

The usual approach to overcome this is to switch off the SCGIHandler based on a LocationMatch or such; therefore the static content has to be named more or less explicitely in httpd.conf.

For CGI and FCGI, the .htaccess file in the rails distribution carries a more elegant solution using Apache Rules: files that are present on the filesystem are considered static content; everything else goes to the rails dispatcher.

Lets try to achieve the same for SCGI:
We cannot do this in the .htaccess file, because for reaching the .htaccess file we first need an Alias to form a filespace directory from the URL-location; and we cannot process the same location by SCGIMount and an Alias.

But we can go the other way round: We do the rule rewriting not on the filespace directory path (in a .htaccess file or Directory container), we do it on the URL-location (in a Location container).

We must do this in httpd.conf.

  • Take care that no Alias directive points to your rails app.
  • Add the following:

<Location /railsapp>
  #... place access restrictions here ...
  RewriteEngine On
  RewriteCond /path-to-your-rails-app/public%{PATH_INFO} -f
  RewriteRule ^.*$ /static-railsapp%{PATH_INFO} [PT]
</Location>
Alias /static-railsapp "/path-to-your-rails-app/public/"

This will route the accesses to existing files into the .htaccess of your rails app just as before.

  • Move all other accesses to SCGI:

SCGIMount /railsapp 127.0.0.1:9999
  • Security Issue: if you have any access restrictions placed in any Directory containers matching your rails app (like SSLrequireSSL or password protections), you must copy these to the new Location container!
    Otherwise the calls to the SCGIServer will bypass these restrictions.
  • start the SCGIserver: go to the main directory of your rails app, su to the unpriviledged user you have chosen and execute _scgi_ctrl start_
  • restart the webserver

This works for me with rails 1.1.6, and at least for low traffic on a small machine it behaves a lot better than FCGI.

BTW: If you need some long-running database updates, you can place an appropriate SCGIServerTimeout value into the Location Container. The default is 60 (secs).

Another page to consider are the easy startup options here

As a note of competing protocols, this blog entry has a comment of one user who has experimented with a few ways to serve rails in production (lightspeed being stronger).

Another note, the author (Zed Shaw) hasn’t worked on this project in awhile (mostly focusing on mongrel), but it still works pretty quickly. [RogerdPack]

Example setup:

httpd.conf


... Listen 8100 <VirtualHost *:8100> # no server name so that requests don't have to specify that server name only to be served :) DocumentRoot /home/rpack/blender/public SCGIMount / 127.0.0.1:9999

  1. tell it where to go to connect to those scgi processes
  2. <Directory /home/rpack//blender>
  3. AllowOverride All

  4. # matches locations with a dot following at least one more characters, that is, things like *,html, *.css, *.js, which should be delivered directly from the filesystem
    <LocationMatch \..+$>
    # don’t handle those with SCGI
    SCGIHandler Off

This is a quick guide to getting Zed Shaw’s SCGI Rails Runner running with Apache2.

Download and install mod_scgi.


# wget <a href="http://quixote.python.ca/scgi.dev/apache2/mod_scgi.c">http://quixote.python.ca/scgi.dev/apache2/mod_scgi.c</a>
# /usr/local/apache2/bin/apxs -i -c mod_scgi.c # others have found "make; make install" to work, as well.

In httpd.conf set up mod_scgi:

LoadModule scgi_module modules/mod_scgi.so

In httpd.conf, inside your <VirtualHost> directive add in the mount point, allow the .htaccess file to have control, and instruct it not to serves files with “.” in them from RoR:


# Tell it to let scgi serve all files [rather large scope, but hey :) ]
SCGIMount / 127.0.0.1:9999
#Tell it not to let the scgi serve non RoR files (we assume files with a dot in them, such as picture.jpg, should be served by apache, not RoR).
    <LocationMatch \..+$>
        # don't handle those with SCGI
        SCGIHandler Off
    </LocationMatch>

Then it’s just a case of running the scgi server:

# cd <railsapp>
# scgi_ctrl config
<enter password you want -- this password is prompted when you restart the server, etc.>
# scgi_ctrl start

Done! (Note that your RoR app will be running in production mode.)

Another way to set it up would be to add the following to inside a virtualhost directive (same effect, slightly more complex).


<Directory /path/to/rails/public/>
        # allow .htaccess files to take effect
        AllowOverride All

  1. Tell it to let scgi serve files called “scgi-bin/whatever”
    SCGIMount /scgi-bin/ 127.0.0.1:9999
    #Tell it not to let the scgi serve non ror files (they probably have a dot in them, so instruct it not to serve them).
    <LocationMatch \..+$>
    # don’t handle those with SCGI
    SCGIHandler Off

and add this rewrite-rule thus: in public/.htaccess, replace the dispatch.(f)cgi line with:

RewriteRule ^(.*)$ /scgi-bin/%{REQUEST_URI} [QSA,L]</pre>
. This

Same effect, except you could use that .htaccess to perhaps add more flexibility for static files, later.

Read the docs for more information on changing to production mode [hint — it defaults to production] and for more scgi_rails options.

Hint
If you want to run multiple apps on different virtual hosts, start the scgi_rails on different ports when configuring scgi_rails (default port is 9999). Then specify the port in your virtual host config in the SCGIMount directive.

More ways to serve static files — Accessing stylesheets, images, javascripts while mounting /

You will also note that some files are instructed not to be served by the scgi handler (static files).

The following setup accomplishes this in a different way than just filtering for “any file with a .” You set a default route for / and don’t have to modify the .htaccess file – dead simple.


<VirtualHost 123.456.789.123>
  ServerName <a href="http://www.domain.com">www.domain.com</a>

  DocumentRoot /hosting/domain.com/public/
  SCGIMount / 127.0.0.1:10000

  <LocationMatch "/(stylesheets|images|javascripts).*$"> 
    SCGIHandler Off
  </LocationMatch>
</VirtualHost>

Also this site explains a way of serving all files with a . in them as static. The bottom of this file also has another example. The InstantRails guys do it thus

Non-root source (i.e., not SCGIMount /, or, how to make http://hostname/subdir point to an RoR app)

Because


Alias /blender "/home/rpack/blender"
SCGIMount /blender/ 127.0.0.1:9999  # note that we can't do this as we already have 'something' applied to blender.  Try it another way

Is not possible (it has two directives for a directory—apache gets confused), you need to do some conniving if you want a host a rails app in a directory.
This page
deals with it, as also the instantrails page on the same.

Some user experiences:
—————————
I am just another guy, and I got it running in a rather complex configuration with Apache2.2. I am using an approach that I have not yet read on the web, so I think I should share it.
I am using FreeBSD 5.5 (this should not matter much) and three different systems for database, apache and SCGIservers (isolated by firewalls). But this approach should run as good on a single server.

I use multiple Rails Applications within the same VirtualHosts, some with and some without SSL (and some must behave differently if accessed with or without SSL).

The RailsApps are in arbitrary places of the filesystem, not within the Apache DocumentRoot.
From the Web my applications are accessed as http://myhost.org/railsapp1, http://myhost.org/railsapp2, etc.

If you want only one application per VirtualHost, it might be not a bad idea to do it the same way and then redirect your RootURL to the actual URL – this could pay off when extensions or upgrades are needed.

How to do:

  • Compile and install the mod_scgi (here it is 1.11) on the Webserver and LoadModule in the httpd.conf.
  • Install scgi_rails GEM (and two prerequisite GEMs) on the SCGIserver.
  • Run _scgi_ctrl config_ in the main dir (nowhere else!) of your rails app, and enter a password for (possibly remote) admin access to the SCGI server.
  • this has created a scgi config file in config/scgi.yaml. There are two ports mentioned, one is where the Webserver calls the SCGIServer for productive action, and the other (the “druby” URL) is the SCGIServer admin access port. You can change them if necessary. (security issue: these ports should not be visible from outside your server/site. Especially the admin port is only secured by a telnet-style plaintext-transmitted password. The admin port can be switched off with an extra option.)
  • If you place your logfile somewhere else than the default location, edit the created entry starting with :logfile:, and then add another line starting with :log_file: and the same location. (This is to workaround a misspelling in the scgi_rails code.)
  • Choose a suitable unpriviledged user to run the SCGI-server. Take care that the logfile and the rails-sessionstore and all the ressources your app needs are accessible by that user.

Now for the special thing: We need static content from the RailsApp public directories, and we need to route application accesses to SCGI. SCGI does the latter with SCGIMount, which takes the respective Location out of the apache namespace and moves it to SCGI, so the static content (stylesheets, etc) gets invisible to the webserver.

The usual approach to overcome this is to switch off the SCGIHandler based on a LocationMatch or such; therefore the static content has to be named more or less explicitely in httpd.conf.

For CGI and FCGI, the .htaccess file in the rails distribution carries a more elegant solution using Apache Rules: files that are present on the filesystem are considered static content; everything else goes to the rails dispatcher.

Lets try to achieve the same for SCGI:
We cannot do this in the .htaccess file, because for reaching the .htaccess file we first need an Alias to form a filespace directory from the URL-location; and we cannot process the same location by SCGIMount and an Alias.

But we can go the other way round: We do the rule rewriting not on the filespace directory path (in a .htaccess file or Directory container), we do it on the URL-location (in a Location container).

We must do this in httpd.conf.

  • Take care that no Alias directive points to your rails app.
  • Add the following:

<Location /railsapp>
  #... place access restrictions here ...
  RewriteEngine On
  RewriteCond /path-to-your-rails-app/public%{PATH_INFO} -f
  RewriteRule ^.*$ /static-railsapp%{PATH_INFO} [PT]
</Location>
Alias /static-railsapp "/path-to-your-rails-app/public/"

This will route the accesses to existing files into the .htaccess of your rails app just as before.

  • Move all other accesses to SCGI:

SCGIMount /railsapp 127.0.0.1:9999
  • Security Issue: if you have any access restrictions placed in any Directory containers matching your rails app (like SSLrequireSSL or password protections), you must copy these to the new Location container!
    Otherwise the calls to the SCGIServer will bypass these restrictions.
  • start the SCGIserver: go to the main directory of your rails app, su to the unpriviledged user you have chosen and execute _scgi_ctrl start_
  • restart the webserver

This works for me with rails 1.1.6, and at least for low traffic on a small machine it behaves a lot better than FCGI.

BTW: If you need some long-running database updates, you can place an appropriate SCGIServerTimeout value into the Location Container. The default is 60 (secs).

Another page to consider are the easy startup options here

As a note of competing protocols, this blog entry has a comment of one user who has experimented with a few ways to serve rails in production (lightspeed being stronger).

Another note, the author (Zed Shaw) hasn’t worked on this project in awhile (mostly focusing on mongrel), but it still works pretty quickly. [RogerdPack]

Example setup:

httpd.conf


... Listen 8100 <VirtualHost *:8100> # no server name so that requests don't have to specify that server name only to be served :) DocumentRoot /home/rpack/blender/public SCGIMount / 127.0.0.1:9999

  1. tell it where to go to connect to those scgi processes
  2. <Directory /home/rpack//blender>
  3. AllowOverride All

  4. # matches locations with a dot following at least one more characters, that is, things like *,html, *.css, *.js, which should be delivered directly from the filesystem
    <LocationMatch \..+$>
    # don’t handle those with SCGI
    SCGIHandler Off