Passenger and Python WSGI

  • The instructions provided in the following section are considered advanced.
  • You are expected to be knowledgeable in the UNIX shell.
  • Support for these instructions is not available from DreamHost tech support.
  • Server changes may cause this to break.
  • Be prepared to troubleshoot this yourself if this happens.

Passenger is best known for being used with Ruby on Rails applications, however it can also serve up Python web applications which use the WSGI interface, including any application which uses the Django framework. Since Passenger allows your application to temporarily reside in memory while it is being actively used, it will allow your site to respond significantly faster than is otherwise possible.

Passenger's WSGI support works reasonably well, however another available option is Python FastCGI in case you run into problems.

Setting up Passenger WSGI

To start an example Python site using Passenger WSGI, your first step should be to configure the domain to use Passenger as shown in the Passenger article. Note that the document root must end in "/public" for a Passenger application as this directory will be used to serve static media.

Once you have set the domain to use Passenger, create a file called passenger_wsgi.py in the folder above the document root (i.e., if you set your document root to /home/username/example.com/public, you'd put this file at /home/username/example.com/passenger_wsgi.py). This file must export a WSGI server with the name application. Here's a minimal example:

def application(environ, start_response):
    start_response('200 OK', [('Content-type', 'text/plain')])
    return ["Hello, world!"]

This application will return a text file with the content "Hello, world!" for any request.

Passenger WSGI and Django

See Django for instructions on how to configure Passenger to run Django.

Passenger WSGI and virtualenv

As Passenger loads your passenger_wsgi.py into a special wrapper (currently /dh/passenger/lib/phusion_passenger/wsgi/request_handler.py, although this may change), you cannot directly select which Python interpreter is used to run your application. However, you can switch interpreters at runtime by adding the following code to the beginning of your passenger_wsgi.py:

import sys, os
INTERP = "/home/<username>/local/bin/python"
#INTERP is present twice so that the new Python interpreter knows the actual executable path
if sys.executable != INTERP: os.execl(INTERP, INTERP, *sys.argv)

Set INTERP to the Python interpreter which you wish to use instead of the default.

Passenger WSGI and Pylons/Pyramid

If you're using a Pyramid-framework supported site, the following should work for your passenger_wsgi.py, assuming you've setup the Python virtual environment at INTERP:

import sys, os
INTERP = "/home/<username>/local/bin/python"
#INTERP is present twice so that the new Python interpreter knows the actual executable path
if sys.executable != INTERP: os.execl(INTERP, INTERP, *sys.argv)

from paste.deploy import loadapp
application = loadapp('config:/home/path/to/site/production.ini')

Note that if you're using a site created from one of the Pyramid starter templates, the development.ini config file wraps your site in the ErrorMiddleware layer, similar to what's done in the next section. However, ErrorMiddleware does not support environments where wsgi.multiprocess is True, so you must use the production config, or modify environ to set wsgi.multiprocess to False. (Note: This may cause problems if you manually override the settings). The following link may offer further assistance:

Pyramid-on-DreamHost

500 Errors with Passenger WSGI workaround

Passenger WSGI at the moment has difficulty dealing with errors. Namely, when your WSGI application (for example, but not limited to, Django) raises an uncaught exception, Passenger dies, a 500 page is displayed in the browser, and the error message is not recorded in the home/username/logs/sitename/http/error.log file. This makes debugging tricky.

One solution is to use Python Paste as a WSGI middleware between passenger and your application:

  1. Grab Paste from here: http://pypi.python.org/pypi/Paste.
  2. Unzip the files. All you need is the "paste" directory.
  3. Put it into your application directory (for example, /home/username/sitename/myapp/paste)
  4. Edit your passenger_wsgi.py file to include that directory in the Python path, and then load Paste.
Here is what your passenger_wsgi.py file might look like:
import sys, os
 cwd = os.getcwd()
 myapp_directory = cwd + '/myapp'
 sys.path.insert(0,myapp_directory)
 sys.path.append(os.getcwd())
 os.environ['DJANGO_SETTINGS_MODULE'] = "myapp.settings"
 from paste.exceptions.errormiddleware import ErrorMiddleware
 import django.core.handlers.wsgi
 application = django.core.handlers.wsgi.WSGIHandler()
 # To cut django out of the loop, comment the above application = ... line ,
 # and remove "test" from the below function definition.
 def testapplication(environ, start_response):
   status = '200 OK'
   output = 'Hello World! Running Python version ' + sys.version + '\n\n'
   response_headers = [('Content-type', 'text/plain'),
                       ('Content-Length', str(len(output)))]
   # to test paste's error catching prowess, uncomment the following line
   # while this function is the "application"
   #raise("error")
   start_response(status, response_headers)    
   return [output]
 application = ErrorMiddleware(application, debug=True)

Local logging alternative

import os, sys

def _get_log():
    return file('/home/<username>/passengerwsgi.log', 'a')

log = _get_log()
print >>log, "Running %s" % (sys.executable)

INTERP = "/home/moatra/local/bin/python"
if sys.executable != INTERP: 
    print >>log, "Detected wrong interpreter location, swapping to %s" % (INTERP)
    #swapping interpreters will not flush any files
    log.flush()
    log.close()
    os.execl(INTERP, INTERP, *sys.argv)
    #Should resume execution from the top of the file

log.flush()
log.close() 

from paste.deploy import loadapp

def application(environ, start_response):
    log = _get_log()
    print >>log, "Application called:"
    print >>log, "environ: %s" % str(environ)
    results = []
    try:
        app = loadapp('config:/home/path/to/site/production.ini')
        print >>log, "App loaded, attempting to run"
        log.flush()
        results = app(environ, start_response)
        print >>log, "App executed successfully"
    except Exception, inst:
        print >>log, "Error: %s" % str(type(inst))
        print >>log, inst.args
        log.flush()
    finally:
        log.close()
    return results

A slightly more robust local logging alternative

This example uses Python's logging module. It does not contain the alternative interpreter bit or loading the ini file. It does show how to load local modules using getcwd().

Apart from the name myappmodule and myappmodule.application, this doesn't make any assumptions about your application.

import os
import sys
import logging
# append current dir to module path
cwd = os.getcwd()
sys.path.append(cwd)
# assuming this module is in the same dir as passenger_wsgi, this now works!
import myappmodule

# create a logfile in the current directory
logfilename = os.path.join(cwd, 'passenger_wsgi.log')
# configure the logging
logging.basicConfig(filename=logfilename, level=logging.DEBUG)
logging.info("Running %s", sys.executable)

def application(environ, start_response):
    logging.info("Application called:")
    logging.info("environ: %s", str(environ))
    results = []
    try:
        results = myappmodule.application(environ, start_response)
        logging.info("App executed successfully")
    except Exception, inst:
        logging.exception("Error: %s", str(type(inst)))
    logging.info("Application call done")
    return results

Another solution

This solution is only available on a VPS machine.

Another solution is to start a development server by executing:

[server]$ ./manage.py runserver

Next, open another SSH shell and run:

[server]$ lynx localhost:8000

This opens your application in the lynx web browser, bypassing Passenger by using the Django development server. If you're lucky, it will return some helpful feedback.

This solution won't help if the problem is with your Passenger configuration since this method bypasses Passenger entirely.

Tips and tricks

Try running your passenger_wsgi.py from your command line which may point out any Python errors.

Passenger seems to use a persistent Python session. After updating passenger_wsgi.py, make sure to run pkill python to reset the session and force the server to use your new changes.

See also

Did this article answer your questions?

Article last updated .