Apache mod_wsgi to an application-local WSGI server with systemd

Introduction

WSGI is a specification that enables communication between webservers (which can be implemented in any language), and frameworks or web applications written in the Python programming language.

This article documents a high-level transition from Apache’s mod_wsgi, to a dedicated WSGI server (in this case, gunicorn), for running Python web applications. systemd is used to manage the WSGI server process.

Apache and mod_wsgi

The Apache HTTP server has for a long time been the Internet’s most popular webserver. It’s stable, well-documented, widely-deployed, and it has a myriad of modules available that extend its functionality.

One such module is mod_wsgi, which allows Apache to act as an application server for Python web applications.

I originally chose mod_wsgi because the alternatives were:

  • To use a distribution-packaged WSGI server (which potentially meant tying my application to a distribution-specific version of a single, non-core piece software).
  • Distribute my application with a WSGI server, which meant having to write custom init scripts (see below).

A simple Apache (with mod_wsgi) config for running a Python web application (note: the config is written using Apache 2.2’s mod_access syntax):

<VirtualHost *:80>
ServerName mywebapp.localdomain

WSGIDaemonProcess mywebapp processes=2 threads=15 display-name=%{GROUP} python-path=/var/www/mywebapp/src:/var/www/mywebapp/virtualenv/lib/python2.7/site-packages
WSGIProcessGroup mywebapp

Alias /static /var/www/mywebapp/src/static

<Directory /var/www/mywebapp/src/static>
    Order deny,allow
    Allow from all
</Directory>

WSGIScriptAlias / /var/www/mywebapp/src/mywebapp/wsgi.py

<Directory /var/www/mywebapp/src/mywebapp>
    <Files wsgi.py>
        Order deny,allow
        Allow from all
    </Files>
</Directory>

</VirtualHost>

WSGI server (gunicorn) with systemd

Python makes it easy (with the help of pip and virtualenv) to distribute WSGI servers such as gunicorn with your application. However writing custom init scripts to manage the WSGI server process can be time-consuming, and error-prone, with the end result being largely distribution specific.

systemd, and the adoption of systemd by the overwhelming majority of Linux distributions has solved a number of problems. systemd makes it very easy to write simple, robust, and portable (across Linux distributions) service files for running programs.

I migrated my Python web applications from mod_wsgi to gunicorn (with systemd) because it greatly simplified my overall configuration, and it allowed for a better separation of concerns.

A simple systemd service file to manage a gunicorn server (running “mywebapp”):

[Unit]
Description=MyWebApp application server

[Service]
WorkingDirectory=/var/www/mywebapp/src/
User=mywebapp
Group=mywebapp

Environment=PYTHONPATH=/var/www/mywebapp/src
ExecStart=/var/www/mywebapp/virtualenv/bin/gunicorn -b localhost:8080 -w 2 mywebapp.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID

[Install]
WantedBy=multi-user.target

Apache is configured to proxy requests to the WSGI server (note: this is using Apache 2.4’s mod_access syntax):

<VirtualHost *:80>
ServerName mywebapp.localdomain

<Directory /var/www/mywebapp/src/static>
Require all granted
</Directory>

ProxyPass /static !
Alias /static /var/www/mywebapp/src/static

ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/

</VirtualHost>

Bonus

I recently attempted to install RatticDB, but in the process encountered a bug with mod_wsgi. I was able to circumvent the issue by substituting mod_wsgi with gunicorn and systemd as discussed in this article.

The text below is the error message that I received. I quote it in the hope that it will lead Google (and people!) to find one possible solution to their problem:

[wsgi:error] [pid XXXXX] [client XXX.XXX.XXX.XXX:XXXXX] Truncated or oversized response headers received from daemon process 'rattic': /opt/RatticWeb/ratticweb/wsgi.py