Deploying Flask Apps with Apache and Mod WSGI

Summary

The built-in Flask web server is useful for testing, but generally is not appropriate for production deployments of Flask web applications. This guide will step you through a straightforward process for serving Flask web apps using the Apache web server with mod_WSGI. It is assumed that you will be deploying on Debian or Ubuntu Linux and using the default system Apache.

High-Level Steps

  1. Install Apache and mod_WSGI.
  2. Setup a directory structure from which to server your application.
  3. Configure your WSGI import file(s).
  4. Write an Apache site configuration for your application.
  5. Enable your application.

Install Apache and mod_WSGI

The following commands will install apache and mod_wsgi on your server. You will need root or sudo privileges to complete this step. If the apache web server isn't yet installed, don't worry. It will be included as a dependency of libapache2-mod-wsgi.

sudo apt-get update
sudo apt-get install libapache2-mod-wsgi

Setup Your Application Directory Structure

For the sake of this guide I will assume that we're installing our application in /home/jsmith. This will be reflected in the example commands and paths. As you're working, make sure to change "jsmith" to your own username. Now let's get started.

First, cd into your home directory, then download and unpack my flask deployment starter tarball.

cd ~
wget https://beagle.whoi.edu/redmine/attachments/download/579/flask_deployment_starter.tar.gz
tar zxvf flask_deployment_starter.tar.gz

This will create the directory ~/public_html. Inside you'll find a few subdirectories, which we'll be using as follows:

~/public_html/apps/

Used to store your python source code. Your application could be installed in any directory that apache can read, but we'll use this as the default. Note that python code SHOULD NOT be installed in a directory from which Apache can serve static files.

~/public_html/http/

Standard location from which to serve non-encrypted static files.

~/public_html/https/

Standard location from which to serve encrypted static files.

~/public_html/logs/

Standard directory to store your application logs.

~/public_html/wsgi/

Directory for .wsgi file(s), which are read by apache and used to call your application(s).

Create Your WSGI Import Files

Included with the flask_deployment_starter.tar.gz are two test applications that you will find in ~/public_html/apps/flasktest. This guide will demonstrate deploying the test applications in four different configurations. We'll start by creating two WSGI import files that will be used by Apache to import the test applications.

Use your favorite text editor to create the files flasktest1.wsgi and flasktest2.wsgi in the directory ~/public_html/wsgi. Make sure to replace "home/jsmith" with the path to your own home directory.

flasktest1.wsgi

import sys
sys.path.insert(0,'/home/jsmith/public_html/apps/flasktest')
from flasktest1 import app as application

flasktest2.wsgi

import sys
sys.path.insert(0,'/home/jsmith/public_html/apps/flasktest')
from flasktest2 import app as application

Add an Apache Site Configuration

Your application directory structure is now setup and you've created your WSGI import files. Now we need to tell the Apache web server about your application. This is done by adding and enabling a new Apache site configuration. This guide will first demonstrate deploying the flasktest1.py application on port 8080, with static files served from the top-level path, the the application served from /flasktest1. We'll also be writing log output from the test application to our own ~/public/logs directory.

To start, create the file flask-test within the directory /etc/apache2/sites-available. You'll need root or sudo access to complete this step. Make sure to update the ServerAdmin email address and replace the six instances of "/home/jsmith" with the path to your own home directory.

flask-test

<VirtualHost *:8080>

        # ---- Configure VirtualHost Defaults ----

    ServerAdmin jsmith@whoi.edu 

        DocumentRoot /home/jsmith/public_html/http

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

        <Directory /home/jsmith/public_html/http/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>

        # ---- Configure WSGI Listener(s) ----

        WSGIDaemonProcess flaskapp user=www-data group=www-data threads=5
        WSGIScriptAlias /flasktest1 /home/jsmith/public_html/wsgi/flasktest1.wsgi 

        <Directory /home/jsmith/public_html/http/flasktest1>
                WSGIProcessGroup flaskapp
                WSGIApplicationGroup %{GLOBAL}
                Order deny,allow
                Allow from all
        </Directory>

        # ---- Configure Logging ----

    ErrorLog /home/jsmith/public_html/logs/error.log
    LogLevel warn
    CustomLog /home/jsmith/public_html/logs/access.log combined

</VirtualHost>

We also need to enable an apache listener on port 8080. We'll do this by editing the /etc/apache2/ports.conf file (sudo required) and adding the following lines.

NameVirtualHost *:8080
Listen 8080

Finally, to deploy the new configuration we need to take two steps. First, run the a2ensite command to enable the new flask-test site configuration, then reload the web server.

sudo a2ensite flask-test
sudo /etc/init.d/apache2 reload

That's it! The test application should now be working.

Confirm Test Application Deployment

We'll use curl to confirm your deployment. Start by running the following command.

curl http://localhost:8080/

Apache should serve the static ~/public_html/http/index.html file, generating the following output.

<html>
<h2>Hello World, static http.</h2>
</html>

Now try...

curl http://localhost:8080/flasktest1

This should produce output from our flasktest1.py test application.

<html>
  <h2>Test Application 1</h2>
</html>

If your server isn't working, go back and double check all of your configurations, paying special attention to the paths. If you make changes, don't forget to reload the web server by doing:

sudo /etc/init.d/apache2 reload

Alternative Deployments

Root Directory Deployment

You may serve your Flask application from the server's root path by following the instructions above, but using this alternative flask-test file. In this case your "http" static file directory will be completely disabled.

flask-test

<VirtualHost *:8080>

        # ---- Configure VirtualHost Defaults ----

    ServerAdmin jsmith@whoi.edu

        DocumentRoot /home/jsmith/public_html/http

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

        <Directory /home/jsmith/public_html/http/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>

        # ---- Configure WSGI Listener(s) ----

        WSGIDaemonProcess flaskapp user=www-data group=www-data threads=5
        WSGIScriptAlias / /home/jsmith/public_html/wsgi/flasktest1.wsgi 

        <Directory /home/jsmith/public_html/http/>
                WSGIProcessGroup flaskapp
                WSGIApplicationGroup %{GLOBAL}
                Order deny,allow
                Allow from all
        </Directory>

        # ---- Configure Logging ----

    ErrorLog /home/jsmith/public_html/logs/error.log
    LogLevel warn
    CustomLog /home/jsmith/public_html/logs/access.log combined

</VirtualHost>

Two Applications on the Same Port

You may use this flask-test file to serve two applications on the same port. In this configuration, each application will respond to it's own path. You'll be able to guess the test URLs by reading the configuration file.

<VirtualHost *:8080>

        # ---- Configure VirtualHost Defaults ----

    ServerAdmin jsmith@whoi.edu 

        DocumentRoot /home/jsmith/public_html/http

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

        <Directory /home/jsmith/public_html/http/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>

        # ---- Configure WSGI Listener(s) ----

        WSGIDaemonProcess flaskapp user=www-data group=www-data threads=5
        WSGIScriptAlias /flasktest1 /home/jsmith/public_html/wsgi/flasktest1.wsgi 
        WSGIScriptAlias /flasktest2 /home/jsmith/public_html/wsgi/flasktest2.wsgi

        <Directory /home/jsmith/public_html/http/flasktest1>
                WSGIProcessGroup flaskapp
                WSGIApplicationGroup %{GLOBAL}
                Order deny,allow
                Allow from all
        </Directory>

        <Directory /home/jsmith/public_html/http/flasktest2>
                WSGIProcessGroup flaskapp
                WSGIApplicationGroup %{GLOBAL}
                Order deny,allow
                Allow from all
        </Directory>

        # ---- Configure Logging ----

    ErrorLog /home/jsmith/public_html/logs/error.log
    LogLevel warn
    CustomLog /home/jsmith/public_html/logs/access.log combined

</VirtualHost>

Two Applications on Different Ports

You may use this flask-test file to serve two applications on two different ports. In this configuration, top level requests to port 8080 will be served by flasktest1.py, and top level requests to port 8081 are served by flasktest2.py.

<VirtualHost *:8080>

        # ---- Configure VirtualHost Defaults ----

    ServerAdmin jsmith@whoi.edu 

        DocumentRoot /home/jsmith/public_html/http

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

        <Directory /home/jsmith/public_html/http/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>

        # ---- Configure WSGI Listener(s) ----

        WSGIDaemonProcess flaskapp1 user=www-data group=www-data threads=5
        WSGIScriptAlias / /home/jsmith/public_html/wsgi/flasktest1.wsgi 

        <Directory /home/jsmith/public_html/http/>
                WSGIProcessGroup flaskapp1
                WSGIApplicationGroup %{GLOBAL}
                Order deny,allow
                Allow from all
        </Directory>

        # ---- Configure Logging ----

    ErrorLog /home/jsmith/public_html/logs/error.log
    LogLevel warn
    CustomLog /home/jsmith/public_html/logs/access.log combined

</VirtualHost>

<VirtualHost *:8081>

        # ---- Configure VirtualHost Defaults ----

        ServerAdmin jsmith@whoi.edu

        DocumentRoot /home/jsmith/public_html/http

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

        <Directory /home/jsmith/public_html/http/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>

        # ---- Configure WSGI Listener(s) ----

        WSGIDaemonProcess flaskapp2 user=www-data group=www-data threads=5
        WSGIScriptAlias / /home/jsmith/public_html/wsgi/flasktest2.wsgi

        <Directory /home/jsmith/public_html/http/>
                WSGIProcessGroup flaskapp2
                WSGIApplicationGroup %{GLOBAL}
                Order deny,allow
                Allow from all
        </Directory>

        # ---- Configure Logging ----

        ErrorLog /home/jsmith/public_html/logs/error.log
        LogLevel warn
        CustomLog /home/jsmith/public_html/logs/access.log combined

</VirtualHost>

In order for this to work, you'll also need to enable apache port 8081. Do this by adding the following lines to /etc/apache2/ports.conf.

NameVirtualHost *:8081
Listen 8081

Deploying Your Own Application

You can easily adapt this recipe to deploy your own applications. Here are the steps:

  1. Create a directory in ~/public_html/apps for your application and copy in your application code.
  2. Create a WSGI import file for your application in ~/public_html/wsgi. Make sure to import your application as "application", and don't forget to update the path to match the directory created in step 1. Note that if your application needs to access other modules that are not available in the standard python system path, simply add the additional paths to your WSGI import file.
  3. Modify one of the sample flask-test site files above and add it to the /etc/apache2/sites-available directory. You'll want to change the file name from "flask-test" to something that represents your application. You can also change the port numbers on the VirtualHost line(s), but don't forget to enable your port in the /etc/apache2/ports.conf file.
  4. If you want, create a subdirectory in ~/public_html/logs to store your application logs, then edit the ErrorLog and CustomLog site configuration lines to use the new directory.
  5. Finally, enable your application using the a2ensite command, then reload the apache server.

Troubleshooting

My app doesn't work at all. What do I do?

Before asking for help, double check all of your path configurations. A single small mistake in your site configuration file can break the app. If you change your site configuration, don't forget to reload the web server.

My WSGI import file and site configuration is correct, but my app isn't running correctly.

Check your ~/public/logs/error.log file. This should contain python stack traces that will help.

The apache logs complain about failed .pyc creation attempts.

By default the apache and WSGI daemon processes will run as the user www-data, which doesn't have write access to your ~/public_html/apps directory. You can create the .pyc files manually by calling your WSGI import configuration from the command line. For example:

cd ~/public_html/wsgi
python flasktest1.wsgi

My app uses the local filesystem, but write access fails when running behind apache.

That's because the www-data user under which the WSGI daemon process is running can't write to your application data directory. Usually the safest way to fix the problem is to grant write access for your application data directory to the group www-data. Alternatively you may edit the WSGIDaemonProcess line in your site configuration file to run the WSGI daemon as a different user, but this may have broader security implications.

My web browser is telling me that it can not connect to the server. What's up?

Are you sure that apache is running? If yes, have you enabled your application port in the /etc/apache2/ports.conf file, and does it match the port number in the opening VirtualHost line of your site configuration? If you've updated your apache configuration, did you remember to reload the web server?

What about my application's static web content?

This is an application design issue as well as a deployment problem. Using the built-in Flask method for serving static files should work fine behind apache, but is inefficient. Instead, you may want to symlink your applications static file directory into ~/public_html/http, then add an alias to your site configuration file to handle your static content calls.

Alias /css/ /home/jsmith/http/myapp/css/

My application fails to load one or more required python modules.

No problem. Simply add the directory path where the required module is stored to your WSGI import file. For example:

sys.path.insert(0,'/home/jsmith/ibt/oii')

Additional Reading

http://flask.pocoo.org/docs/deploying/mod_wsgi/
http://www.lonesomedev.com/?p=169
http://stackoverflow.com/questions/5965870/changing-default-url-to-static-media-in-flask