Using Python with Nginx via Passenger

Setting up Nginx

If you have previously had Nginx installed, and used the same location as before, then there’s a chance you do not need to mess with init.d. However if you need start/stop capability as well as allowing the server to auto start upon a reboot then perform the following steps. (If you installed nginx to a location other than /opt/nginx you may need to edit the file for the correct paths)

cd /etc/init.d
wget http://kbeezie.com/examples/nginx
chmod +x /etc/init.d/nginx
/usr/sbin/update-rc.d -f nginx defaults
/etc/init.d/nginx start

Now you can start, stop or restart nginx easily.

Configuring Nginx

By default the Nginx configuration file (/opt/nginx/conf/nginx.conf) looks something like this (commented lines were removed). Remember, Passenger already added the needed lines to the http { } block in your actual nginx.conf file.

worker_processes  1;
 
events {
    worker_connections  1024;
}
 
http {
    include       mime.types;
    default_type  application/octet-stream;
 
    sendfile        on;
    keepalive_timeout  65;
	gzip  on;
 
    server {
        listen       80;
        server_name  localhost;
 
        location / {
            root   html;
            index  index.html index.htm;
        }
 
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

Which would show you something like this when you visit the browser:
Welcome to Nginx

So we know it works… but its pretty much just pure static content. Let us change the default localhost into a Python application using Passenger. For this we’re going to use the default web folder provided by Nginx, the only difference is, we’ll need to create a Public folder under it.

cd /opt/nginx/html
mkdir public
vi passenger_wsgi.py

You don’t have to use vi to create your test Python script, you might even be an emacs guy cursing me for suggesting such monstrosity ^_^. The Python app below will simply output environment variables to the browser.

import sys
import os
 
def application(environ, start_response):
	start_response("200 OK", [])
	ret = ["%s: %s\n" % (key, value)
		for key, value in environ.iteritems()]
	return ret

Do note however, that the file is outside of the /public location, and that it is named passenger_wsgi.py. Placing the file elsewhere, or giving it a different filename will cause it not to be loaded by Passenger.

Now we need to tweak our Nginx configuration to load the Python WSGI application.

worker_processes  1;
 
events {
    worker_connections  1024;
}
 
http {
    include       mime.types;
    default_type  application/octet-stream;
 
    sendfile        on;
    keepalive_timeout  65;
	gzip  on;
 
	passenger_root /usr/local/lib/ruby/gems/1.9.1/gems/passenger-2.2.5;
	passenger_ruby /usr/local/bin/ruby;
 
    server {
        listen       80;
        server_name  localhost;
		root /opt/nginx/html/public/; #the folder name public must always be used
		passenger_enabled on; #this actually turns passenger on at this location
    }
}

A couple things you’ll notice, the passenger configuration parameters were already added by Passenger in the http { } block. In the above you’ll notice we don’t even use a location block in the server { } block. At a bare minimum we need the server_name, root path, and passenger_enabled line. (you don’t even need the listen 80; since 80 is the default port unless otherwise specified in the http {} block)

We’ll need to restart nginx for the changes to take effect.

# /etc/init.d/nginx restart
restarting nginx: nginx.

Load up the server in the browser, and you should be presented with enviroment outputs served up by your Python WSGI application:

Success

You are now setup to easily deploy Python WSGI applications from Nginx. If you make a change to the script, you may need to restart nginx, as the application is stored in memory.

Using DJango and Web.py with Passenger

You’re probably wondering how would you deploy a framework such as Django or Web.py, with Passenger, especially since the main file must always be called passenger_wsgi.py. Instead of the test script above, you would simply use the code below and place your Django application in the same folder (above public). For example in the /opt/nginx/html folder you may see the following folders : django, public, and a folder with your django application name with the file passenger_wsgi.py along side them.

import os, sys
nginx_configuration= os.path.dirname(__file__)
project = os.path.dirname(nginx_configuration)
workspace = os.path.dirname(project)
sys.path.append(workspace) 
 
os.environ['DJANGO_SETTINGS_MODULE'] = 'testapp.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

Detailed Guide : Click for a more detailed guide to setting Django up with Passenger and Nginx

With Web.Py the documentation often calls for web.application which is self-hosted.

app = web.application(urls, globals())
if __name__ == "__main__": app.run()

Below is an example Web.py app as stored in a passenger_wsgi.py file. You’ll note that instead of the two lines above, it is now a single line with the modified function assigned to application. This is covered in Web.py’s documentation regarding Apache and mod_wsgi, so this modification should work with most WSGI servers.

import web
 
urls = (
  '/', 'index'
)
 
application = web.application(urls, globals()).wsgifunc()
 
class index:
    def GET(self):
        return "Hello, world!"

Further Information

For further information regarding Nginx itself, I strongly recommend taking a look at the Wiki

For more detailed instructions on using Passenger with Nginx, refer to the User Manual. The user manual will be useful if you wish to take it a step further and venture a look at building Ruby applications, of which you already have capabilities installed.

5 comments

  1. Hongli Lai says:

    Hi kbeezie, good writeup. 🙂 I just published a blog article about Phusion Passenger and Python today. Maybe you’d care to comment? http://izumi.plan99.net/blog/index.php/2009/11/21/phusion-passenger-for-python/

    “WeÂ’ll need to download and compile Ruby version 1.9.1 patch level 129. While this is not the latest, IÂ’ve had unconfirmed reports that anything newer than p129 is not fully compatible with Passenger.”

    This is correct. Later versions of 1.9.1 have bugs in the ‘tempfile’ library. These bugs have been fixed in Ruby’s SVN repository but so far there hasn’t been a 1.9.1 release containing the bug fixes.

    By the way, it’s not necessary to install Rails in order to use Phusion Passenger. Rails is only required if you deploy Rails apps.

  2. vanderkerkoff says:

    kbeezie, form some reason sudo is not finding ruby

    any ideas?

  3. kbeezie says:

    Without using sudo, try executing ruby -v to see if anything shows up at all, if not try ‘which ruby’. If nothing still shows up then either the PATH isn’t configured correctly to Ruby, or Ruby itself isn’t properly installed. ‘sudo’ itself doesn’t find commands per se, but is simply a way of saying ‘I want to run the following command as a super-user or root’.

  4. Jay States says:

    Hrm? Why, I’ll assuming that you’ve not seen the uwsgi project? This is much better for python/django/pylons – http://projects.unbit.it/uwsgi/

    Good Luck

  5. kbeezie says:

    Note the date of Nginx’s support for uwsgi and the date of said article.