Set up a Django application with the Apache web server and WSGI

Problem

You want to set up a Django application in production with the Apache web server and WSGI.

Solution

Install Apache and the mod_wsgi module. Configure the mod_wsgi module to locate your Django application and Python virtualenv with the WSGIPythonPath parameter.

Create a separate Apache instance to serve static content for your Django application and update the application's STATIC_URL variable to pick-up static content from this Apache instance. Alternatively, you can define Apache Alias directives to leverage the same Apache instance running WSGI to also serve static content for the same Django application.

How it works

Apache is one of the most popular web servers to deploy web applications in production environments. It supports a wide array of applications written in different programming languages, so the core server is one of the most robust and secure offerings in the market. For Python applications, the Apache server supports WSGI through the mod_wsgi module. WSGI or 'Web Server Gateway Interface' is the Python programming language standard approach that defines a simple and universal interface between web servers and Python web applications or frameworks.

Note Avoid using mod_python, FastCGI and CGI

Besides mod_wsgi, Apache can also run Python applications with other approaches that you should avoid. CGI or 'Common Gateway Interface' was the first approach -- dating back to 1993 -- to run web applications in general. CGI is extremely inefficient because it requires to spawn a language interpreter (e.g. Python or other) to run the web application code on behalf of the web server. FastCGI is an improved version of CGI -- hence the preceding 'Fast' in its name -- but which nevertheless still provides sub-optimal conditions to run Python web applications.

mod_python emerged to address the shortcomings of CGI/FastCGI. mod_python embeds a Python interpreter into the Apache web server, which makes applications run faster than traditional CGI/FastCGI (e.g. Apache keeps things 'closer' in its processes, things like database connections and other data between hits and processing). However, mod_python's main advantage can also be a drawback, particularly for high-traffic applications. Because a Python interpreter is loaded into Apache's processes, the Apache processes become heavyweight and can suffer handling high amounts of traffic. In addition, mod_python is an Apache specific solution, which therefore can't be used with other web servers.

Because WSGI is the standard set forth by the Python language (http://www.python.org/dev/peps/pep-3333/) to promote common ground for portable communication between Python applications and web servers, it should be the preferred approach. And in the case of Apache, this means making use of the mod_wsgi module.

Install and configure mod_wsgi

The easiest way to install mod_wsgi is through an OS package manager like apt-get on Debian or Ubuntu. You can execute the command apt-get install libapache2-mod-wsgi to trigger the installation. If you're on a different OS, then you'll need to download the mod_wsgi source code from http://code.google.com/p/modwsgi/downloads/list and follow the installation instructions.

Once you install mod_wsgi using any of the previous methods, you'll need to verify it's loaded on Apache. To verify which modules are loaded in Apache execute the command apache2ctl -t -D DUMP_MODULES as illustrated in listing 1. In the list you should see a line wsgi_module (shared).

Listing 1 - Apache loaded modules, verify the inclusion of wsgi_module.

[user@~]$ apache2ctl -t -D DUMP_MODULES
Loaded Modules:
 core_module (static)
 log_config_module (static)
 logio_module (static)
 version_module (static)
 mpm_worker_module (static)
 http_module (static)
 so_module (static)
 alias_module (shared)
 auth_basic_module (shared)
 authn_file_module (shared)
 authz_default_module (shared)
 authz_groupfile_module (shared)
 authz_host_module (shared)
 authz_user_module (shared)
 autoindex_module (shared)
 cgid_module (shared)
 deflate_module (shared)
 dir_module (shared)
 env_module (shared)
 mime_module (shared)
 negotiation_module (shared)
 reqtimeout_module (shared)
 setenvif_module (shared)
 status_module (shared)
 wsgi_module (shared)
Syntax OK

If you don't see the wsgi_module (shared) line after you execute the command in listing 1, it means the mod_wsgi module still isn't loaded in Apache, even though it may be installed. To load Apache modules in an OS like Debian or Ubuntu you can use the a2enmod command (e.g.a2enmod wsgi). If you use another OS, consult the Apache documentation on how to load shared modules.

Once you're sure the mod_wsgi module is loaded into Apache, you'll need to configure a parameter specific to the Django application and Python virtualenv. If you're not using Python virtualenv or don't know what it's, I strongly recommend you use it because it makes application deployment much cleaner and simpler. The recipe Install Django contains full details on how to use Python virtualenv with Django.

mod_wsgi requires access to a project's Python modules (i.e.files). The way Python locates modules is through an environment variable called PYTHONPATH which defines directories for the Python interpreter to look in. If a Python module is located in a directory not defined in PYTHONPATH, then the Python interpreter won't be able to locate it. The same thing happens with mod_wsgi, if you don't tell mod_wsgi where the Django application and Python packages are located, it won't be able to run correctly.

mod_wsgi uses the WSGIPythonPath parameter to define directories in which to search for Python modules. If you're using Apache on an OS like Debian or Ubuntu, the mod_wsgi has a configuration file called wsgi.conf located in the directory /etc/apache2/mods-available/ to configure this and other mod_wsgi parameters. For other OS Apache installations and if mod_wsgi is enabled, you should find the mod_wsgi configuration in an Apache configuration file surrounded by the XML tags <IfModule mod_wsgi.c>. Listing 2 illustrates a sample setup with the WSGIPythonPath parameter.

Listing 2 - Apache mod_wsgi module configuration.

<IfModule mod_wsgi.c>

   WSGIPythonPath  /python/djangorecipes/lib/python2.7/site-packages/:/www/STORE/

</IfModule>

Notice in listing 2 how the value of the WSGIPythonPath parameter is a directory list separated by :. The first directory /python/djangorecipes/lib/python2.7/site-packages/ corresponds to a Python virtualenv directory -- which gives mod_wsgi access to Django modules installed in the virtualenv. The second directory /www/STORE/ corresponds to a BASE_DIR of a Django project or where a project's manage.py file is located. Under /www/STORE/ you would also find a sub-directory for the project name or PROJECT_DIR (e.g. /www/STORE/coffeehouse/) and inside this sub-directory you would find Django's standard project files like settings.py and wsgi.py.

There are many other WSGI* type variables you can modify to customize mod_wsgi, but for the moment you only need to set WSGIPythonPath to get everything running.

Note Don't define WSGIPythonPath in Apache site configurations

Although it's possible to define the mod_wsgi WSGIPythonPath parameter in an Apache site configuration, this can lead to Apache failing to load Python modules (i.e. the WSGIPythonPath parameter gets read after Apache mod_wsgi tries to load certain Python modules). To guarantee all Python modules are available to an application, define the WSGIPythonPath parameter in the mod_wsgi configuration so it gets read when the module is loaded.

Define an Apache site configuration to serve a Django application with mod_wsgi

Once you configure and setup mod_wsgi for your Django application, you can setup an Apache site configuration. Listing 3 illustrates an Apache virtual server site configuration.

Listing 3 - Apache site virtual server configuration.


<VirtualHost *:80>
	ServerName www.coffeehouse.com
	ServerAlias coffeehouse.com
	ServerAdmin webmaster@coffeehouse.com

        <Directory />
         Options FollowSymLinks
         AllowOverride None
        </Directory>

	WSGIScriptAlias / /www/STORE/coffeehouse/wsgi.py

	ErrorLog ${APACHE_LOG_DIR}/error.log

	LogLevel warn

	CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

The only Python/Django specific thing about the Apache site configuration in listing 3 is the WSGIScriptAlias line. This line tells Apache to mount the application root / with the /www/STORE/coffeehouse/wsgi.py script, the last of which corresponds to the WSGI script of a Django project. You can add any other Apache site configuration as you would for any other Apache site. Also note the wsgi.py script is created by default when you start a Django project and there's no need to change anything in this file for a standard setup.

Once you enable the site configuration and restart Apache, point a browser to the Apache address to access the Django application. What do you see ? If you see the application, albeit without static resources (e.g. Images, CSS, JavaScript) you're ok, I'll describe how to display static resources in the next section. If you get an Apache error, check the Apache logs. Table 1 contains a list of the most common errors and fixes for WSGI deployments.

Table 1 - Common WSGI errors and fixes
ErrorFixes
ImportError: No module named django.core.wsgi
(Cause: Apache can't locate the Django installation)
  • Verify WSGIPythonPath has the route to a Python interpreter (preferably a virtualenv) with a Django installation -- see example in listing 2
  • Check the permissions for the Django installation files and confirm they're accessible by the user that owns Apache.
ImportError: Could not import settings '<project_name>.settings' (Is it on sys.path?): No module named <project_name>.settings
(Cause: Apache can't locate the Django application)
  • Verify WSGIScriptAlias has the route to the Django application -- see example in listing 3
  • Check the permissions for the Django application files and confirm they're accessible by the user that owns Apache.

Set up static resources for a Django application deployed in Apache

By design, static resources (e.g. Images, CSS, JavaScript) in Django are not intended to be served by the same web server that dispatches dynamic application content via WSGI. The reason behind this is that using the same process to dispatch both dynamic content and static content is wasteful and places an additional load on the main web server running WSGI. In addition, since static resources and dynamic data behave differently, it can also be helpful to use separate web servers for each kind of content to implement different web server configurations (e.g. caching, security, etc).

When you use Django's built-in web server (i.e. python manager.py runserver) and have DEBUG=True, the application's static resources are mounted as a convenience under the URL definition of the STATIC_URL variable -- which defaults to /static/. The use of the STATIC_URL variable and this convenience process is described in the recipe Set up static web page resources -- Images, CSS, JavaScript. However, as soon as you set DEBUG=False with Django's built-in web server or change to a different web server altogether (e.g. Apache), this convenience disappears and no static resources are served automatically.

Next, I'll describe two Apache alternatives. One uses two separate Apache server instances -- one for WSGI and the other for static resources -- which is the option I strongly recommend. And the other option describes how to setup an Apache WSGI server instance to also dispatch a Django application's static resources, in case you can't run multiple Apache instances on your hosting service or you simply don't want to monitor multiple server instances.

Keep in mind the following steps assume you have already consolidated your static resources into a single folder defined in STATIC_ROOT. If you haven't made this consolidation or don't know what STATIC_ROOT is, read the full recipe Set up static web page resources -- Images, CSS, JavaScript and pay special attention to the last section 'Set up static resources in a production environment (DEBUG=True)'.

Listing 4 illustrates a modified version of the Apache site configuration in listing 3. Listing 4 shows multiple Apache site configurations, one to run the main (WSGI) Django application and the other to run Django's static resources.

Listing 4 - Multiple Apache site configuration to run static resources on separate instances.


<VirtualHost *:80>
	ServerName www.coffeehouse.com
        ServerAlias coffeehouse.com
	ServerAdmin webmaster@coffeehouse.com

        <Directory />
         Options FollowSymLinks
         AllowOverride None
        </Directory>

	WSGIScriptAlias / /www/STORE/coffeehouse/wsgi.py

	ErrorLog ${APACHE_LOG_DIR}/error.log

	LogLevel warn

	CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>


<VirtualHost *:80>
	ServerName static.coffeehouse.com
	ServerAdmin webmaster@coffeehouse.com

        DocumentRoot /www/STORE/coffeestatic/

        <Directory />
         Options FollowSymLinks
         AllowOverride None
        </Directory>


        <Directory /www/STORE/coffeestatic/>
         Order allow,deny
         Allow from all
        </Directory>


	ErrorLog ${APACHE_LOG_DIR}/static_error.log

	LogLevel warn

	CustomLog ${APACHE_LOG_DIR}/static_access.log combined
</VirtualHost>

The first site configuration in listing 4 belongs to the WSGI server instance and is identical to the one described in listing 3, so there's isn't much left to explain about this configuration.

The second site configuration in listing 4 is used to dispatch static resources through the static.coffeehouse.com domain. The DocumentRoot directive tells Apache to dispatch resources located in the server's file system /www/STORE/coffeestatic/, which represents the consolidation folder for static resources defined in STATIC_ROOT. This means if a request is made to the /css/bootstrap.css URL or the full URL http://static.coffeehouse.com/css/bootstrap.css, Apache dispatches the file under the server's file system /www/STORE/coffeestatic/css/bootstrap.css.

The remaining <Directory> directives are just a standard Apache configurations to define permissions on the files located under /var/www/. Where as the ErrorLog, LogLevel and CustomLog directives are standard Apache logging configurations.

Although the previous approach of using separate web servers for dynamic and static content is ideal, sometimes you may not want or cant run multiple Apache instances. For such a scenario you can use a single web server instance to handle all traffic.

Listing 5 illustrates a modified version of the Apache site configuration in listing 3, which is the 'quick & dirty' approach of dispatching static resources with the same WSGI instance. Use this option only if you can't or don't want to run multiple instances.

Listing 5 - Single Apache site configuration to run static resources on same instance.


<VirtualHost *:80>
	ServerName www.coffeehouse.com
	ServerAlias coffeehouse.com
	ServerAdmin webmaster@coffeehouse.com

        <Directory />
         Options FollowSymLinks
         AllowOverride None
        </Directory>

         Alias /robots.txt /www/STORE/coffestatic/robots.txt
         Alias /favicon.ico /www/STORE/coffeestatic/favicon.ico

         Alias /static/ /www/STORE/coffeestatic/


        <Directory /www/STORE/coffeestatic/>
         Order allow,deny
         Allow from all
        </Directory>

	WSGIScriptAlias / /www/STORE/coffeehouse/wsgi.py

	ErrorLog ${APACHE_LOG_DIR}/error.log

	LogLevel warn

	CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

The Alias /robots.txt /www/STORE/coffeestatic/robots.txt line in listing 5 tells Apache that when a URL request comes in for /robots.txt -- or full URL www.coffeehouse.com/robots.txt -- that it dispatch the file located in the server's file system /www/STORE/coffeestatic/robots.txt -- where /www/STORE/coffeestatic/ represents the consolidation folder for static resources defined in STATIC_ROOT. The other Alias line uses the same technique but to dispatch requests made to the /favicon.ico URL.

The Alias /static/ /www/STORE/coffeestatic/ line is different because it maps full-directory requests made to the /static/ URL to the server's file system /www/STORE/coffeestatic/. This means if a request is made to the /static/bootstrap/bootstrap.css URL, Apache dispatches the file under the server's file system /www/STORE/coffeestatic/bootstrap/bootstrap.css.

Essentially what the Apache Alias directive does is intercept certain URLs and dispatch files from the server's file system. The remaining <Directory> directive is just a standard Apache configuration to define permissions on the files located under /www/STORE/coffeestatic/.

Once you setup Apache to serve the static resources in either of the previous configurations under /www/STORE/coffeestatic/ remember you'll also need to update the STATIC_URL variable in your project's settings.py file to reflect this change. The recipe Set up static web page resources -- Images, CSS, JavaScript contains more details on this last step.

Note AWS S3 and WhiteNoise offer good alternatives to dispatch Django static files bypassing web server configurations

Services like AWS S3 offer turn-key solutions to host static content without the overhead of system administration, see Automate deployment of Django static files (Images, CSS, JavaScript) to Amazon S3 for more details.

In addition, Python packages like WhiteNoise offer a novel approach to serve static content with a focus on optimization from the same WSGI server that dispatches dynamic Django pages, see Optimize serving Django static files (Images, CSS, JavaScript) with WSGI using WhiteNoise for more details.