TL;DR - If you have the choice I found it simpler to use nginx and gunicorn for your prod server. Simple is better than complex

What is ModWSGI?

An apache module which can host any python application supporting the WSGI spec - the python web server gateway. A WSGI is a specification of a generic API for mapping between an underlying web server and a Python web application PEP3333


  • As an apache module
  • As a python package, which install mod_wsgi into your environment. THe binary program mod_wsgi-express then becomes available on the command line. - does not require any configuration of apache yourself

Getting Started

Start off with a hello world app to verify that your mod_wsgi set up is working, then move on to a framework based project.

Place the following in a file (site.wsgi) in your web root:

def application(environ, start_response):
    status = '200 OK'
    output = b'Hello World!'

    response_headers = [('Content-type', 'text/plain'),
                        ('Content-Length', str(len(output)))]
    start_response(status, response_headers)

    return [output]

The user running apache usually www-data will need read permission on this file.

Note that mod_wsgi requires that the WSGI application entry point be called application

Mounting the WSGI Application

This is done in a similar manner to other CGI applications using WSGIScriptAlias

WSGIScriptAlias / /var/www/app/site.wsgi

The first argument is the URL mount point, the second argument is the absolute pathname to the WSGI application script file

This directive can only appear in the main Apache configuration files. It cannot be used within either of the Location, Directory or Files container directives, nor can it be used within a “.htaccess” file.

Note that it is highly recommended that the WSGI application script file in this case NOT be placed within the existing DocumentRoot for your main Apache installation, or the particular site you are setting it up for. This is because if that directory is otherwise being used as a source of static files, the source code for your application might be able to be downloaded.

Then add the following configuration (sudo vim /etc/apache2/sites-available/app.conf):

<VirtualHost *:80>

    DocumentRoot /var/www/app

    WSGIScriptAlias / /var/www/app/site.wsgi


Now enable the site sudo a2ensite app.conf and reload apache service apache reload

Visit the website at or whereever you pointed it and see Hello World!

Loading it to the root above would mean that requests to static files for example: favicon.ico would be processed by the wsgi application.

In this case you would need to rempa the URL’s using aliases:

Alias /robots.txt /usr/local/www/documents/robots.txt
Alias /favicon.ico /usr/local/www/documents/favicon.ico

Alias /media/ /usr/local/www/documents/media/

Delegating to a daemon process

By default any WSGI app is run in embedded mode - it will be run within the apache worker processes used to handle normal file requests. In this mode any applcation changes would require a server restart - which is annoying. To avoid this you can use daemon mode.

In Daemon mode, a set of processes are created for the app, any requests are routed to the processes.

To use daemon mode: WSGIDaemonProcess and WSGIProcessGroup would need to be defined.

Eg: 2 multithreaded processes

WSGIDaemonProcess processes=2 threads=15

Changing our existing embedded method:

<VirtualHost *:80>

    DocumentRoot /var/www/app

    WSGIDaemonProcess processes=2 threads=15 display-name=%{GROUP}

    WSGIScriptAlias / /var/www/app/site.wsgi



Apache error logs for more detailed descriptions

The default Apache LogLevel be increased from warn to info is also a good idea.

LogLevel info

Virtual Environments

You use the python-home argument to specify the virtual environment folder from which to run your application. You can also use python-path to specify colon seperated values to add to your search path.


WSGIDaemonProcess python-home=/var/www/example_site/env python-path=/var/www/example_site

When using a Python virtual environment with mod_wsgi, it is very important that it has been created using the same Python installation that mod_wsgi was originally compiled for

You cannot for example force mod_wsgi to use a Python virtual environment created using Python 3.5 when mod_wsgi was originally compiled for Python 2.7

Compiling mod_wsgi from source to match your virtual env you use to run your application

So I tried to install mod_wsgi with python 3.6, which means I needed to download the release and compile it from source with:

cd /opt/mod_wsgi-4.6.5
sudo ./configure --with-python=/usr/local/bin/python3.6
sudo make
sudo make test
sudo make install

but I got this error:

/usr/bin/ld: /usr/local/lib/libpython3.6m.a(abstract.o): relocation R_X86_64_32S against `_Py_NotImplementedStruct' can not be used when making a shared object; recompile with -fPIC
/usr/local/lib/libpython3.6m.a: error adding symbols: Bad value
collect2: error: ld returned 1 exit status
apxs:Error: Command failed with rc=65536

So according to this answer I need to recompile my python version with --enable-shared:

tar -xzvf Python-3.6.8.tgz
cd Python-3.6.8.tgz
./configure --enable-shared
sudo make install

Successful, yet when I run:

root@web:/opt/Python-3.6.8# python3.6 -V
python3.6: error while loading shared libraries: cannot open shared object file: No such file or directory

So it can’t find the libraries, which I used this answer to fix:

./configure --enable-shared \
            --prefix=/usr/local \

That worked!

I recompiled mod_wsgi and that was successful:


Now I check the apache configuration and…

sudo apachectl configtest

apache2: Syntax error on line 248 of /etc/apache2/apache2.conf: Syntax error on line 1 of /etc/apache2/mods-enabled/wsgi.load: Cannot load /usr/lib/apache2/modules/ into server: cannot open shared object file: No such file or directory

You can view the linked libraries with (taken from this blog post):

root@web:/var/log/apache2# ldd /usr/lib/apache2/modules/ (0x00007ffd57fbe000) => not found => /lib/x86_64-linux-gnu/ (0x00007fd3fb0f4000) => /lib/x86_64-linux-gnu/ (0x00007fd3faef0000) => /lib/x86_64-linux-gnu/ (0x00007fd3faced000) => /lib/x86_64-linux-gnu/ (0x00007fd3fa9ec000) => /lib/x86_64-linux-gnu/ (0x00007fd3fa641000)
/lib64/ (0x00007fd3fb54e000)

Importantly the => not found

I should have read this:

If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR'
flag during linking and do at least one of the following:
- add LIBDIR to the `LD_LIBRARY_PATH' environment variable
    during execution
- add LIBDIR to the `LD_RUN_PATH' environment variable
    during linking
- use the `-Wl,-rpath -Wl,LIBDIR' linker flag
- have your system administrator add LIBDIR to `/etc/'

That file is in: /usr/local/lib/ so we must recompile mod_wsgi again but with specifiying the LD_RUN_PATH

cd /opt/mod_wsgi-4.6.5
sudo ./configure --with-python=/usr/local/bin/python3.6
sudo LD_RUN_PATH=/usr/local/lib make
sudo make install


make: Nothing to be done for 'all'.

It did nothing…Ah you have to say:

make clean


root@web:/opt/mod_wsgi-4.6.5# apachectl configtest
Syntax OK