Skip to main content

Ubuntu 18.04.1, ISPConfig3, Python 3, Flask, Apache 2, and mod_wsgi

Today I spent a lot of time trying to figure out how to get a Flask application started using Python 3 on Ubuntu 18.04.1. I had previously built an application using Python 2.7, Flask, and mod_wsgi, but it had been a while and the documentation I came across just wasn't connecting the dots properly. Here's my notes after the endeavor.

For the example, the root directory of the ISPConfig3 user is going to be /var/www/clients/client1/web1/.

# install 
sudo apt install libapache2-mod-wsgi-py3 python3-pip
# disable mod_python if it's installed, or just uninstall it as mod_wsgi can't be loaded at the same time

# install python3 virtualenv globally
sudo pip3 install virtualenv

# change to the private folder where we'll create the project
cd /var/www/clients/client1/web1/private/
# create the project folder and change into it
mkdir project1 && cd project1
# create the virtual environment in the env directory, which will be created for us
virtualenv env
# activate the virtual environment
source env/bin/activate
# install flask
pip install flask
# setup some other directories and empty files
mkdir app log
touch wsgi.py run.py config.py app/__init__.py

wsgi.py

import sys
sys.path.insert(0, "/var/www/clients/client1/web1/private/project1")

from app import app as application

run.py

# WSGI Server for Development
# Use this during development vs. apache. Can view via [url]:8001
# Run using virtualenv. 'env/bin/python run.py'
from app import app

app.run(host='0.0.0.0', port=8001, debug=True)

app/__init__.py

from flask import Flask
app = Flask(__name__)

# Configurations
#app.config.from_object('config')

@app.route('/')
def hello_world():
    return 'Hello, World!'

Apache virtual host configurationconfiguration. Because of the way the ISPConfig3 vhost template file is configured, I had to invert my logic and use IfDefine !ProtocolHTTPS instead of IfDefine ProtocolHTTP.

<VirtualHost *:80>
Define ProtocolHTTP
ServerName test.domain.com
ServerAdmin webmaster@test.domain.com

ErrorLog /var/log/ispconfig/httpd/test.domain.com/error.log

<IfDefine ProtocolHTTP!ProtocolHTTPS>
WSGIDaemonProcess client1web1 python-home=/var/www/clients/client1/web1/private/project1/env python-path=/var/www/clients/client1/web1/private/project1
</IfDefine>
WSGIProcessGroup client1web1
WSGIScriptAlias / /var/www/clients/client1/web1/private/project1/wsgi.py
WSGIPassAuthorization On

<Directory /var/www/clients/client1/web1/private/project1>
    WSGIProcessGroup client1web1
    WSGIApplicationGroup %{GLOBAL}
	WSGIScriptReloading On
    Require all granted
</Directory>

</VirtualHost>

<VirtualHost *:443>
Define ProtocolHTTPS

ServerName test.domain.com
ServerAdmin webmaster@test.domain.com

ErrorLog /var/log/ispconfig/httpd/test.domain.com/error.log

<IfModule mod_ssl.c>
	SSLEngine on
	SSLCertificateFile /var/www/clients/client1/web1/ssl/test.domain.com-le.crt
	SSLCertificateKeyFile /var/www/clients/client1/web1/ssl/test.domain.com-le.key
	SSLCertificateChainFile /var/www/clients/client1/web1/ssl/test.domain.com-le.bundle
</IfModule>

# The WSGIDaemonProcess directive can only defined once, and you don't want two daemons for the same application.
# This is currently what is preventing my from being able to configure this type of application from the ISPConfig3 web interface.
# I've attempted to use the IfDefine conditional but I still get the same error message "Name duplicates previous WSGI daemon definition."
# I don't think the parser is smart enought to realize that its not actually going to use this directive because in this VirtualHost we've define
# ProtocolHTTPS, NOT ProtocolHTTP.
#<IfDefine ProtocolHTTP!ProtocolHTTPS>
#WSGIDaemonProcessWSGIDaemonProcess client1web1 python-home=/var/www/clients/client1/web1/private/project1/env python-path=/var/www/clients/client1/web1/private/project1
#</IfDefine>
WSGIProcessGroup client1web1
WSGIScriptAlias / /var/www/clients/client1/web1/private/project1/wsgi.py
WSGIPassAuthorization On

<Directory /var/www/clients/client1/web1/private/project1>
    WSGIProcessGroup client1web1
    WSGIApplicationGroup %{GLOBAL}
	WSGIScriptReloading On
    Require all granted
</Directory>

UnDefine ProtocolHTTPS
</VirtualHost>

Diff of the vhost.conf.master file.

# diff -u vhost.conf.master.1 vhost.conf.master
--- vhost.conf.master.1 2018-08-23 03:50:52.539521052 -0500
+++ vhost.conf.master   2018-08-23 03:43:36.470905313 -0500
@@ -12,6 +12,9 @@

 <tmpl_loop name='vhosts'>
 <VirtualHost {tmpl_var name='ip_address'}:{tmpl_var name='port'}>
+<tmpl_if name='ssl_enabled'>
+Define ProtoHTTPS
+</tmpl_if>
 <tmpl_hook name='apache2_vhost:vhost_header'>
 <tmpl_if name='php' op='==' value='suphp'>
                DocumentRoot <tmpl_var name='web_document_root'>
@@ -524,6 +527,9 @@

 <tmpl_var name='apache_directives'>
 <tmpl_hook name='apache2_vhost:vhost_footer'>
+<tmpl_if name='ssl_enabled'>
+UnDefine ProtoHTTPS
+</tmpl_if>
 </VirtualHost>

 <tmpl_if name='apache_version' op='>=' value='2.4' format='version'>

Still to do:

  • Figure out how to configure this properly using just the ISPConfig3 control panel. I can make it work editing the config files by hand, but they eventually get overwritten by ISPConfig3. The big problem I have right now is the WSGIDaemonProcess directive can only exist once... not in both the http and https VirtualHost definitions. This was resolved as shown above.

Source material:

  • coxley/flask-file-structure (Source)
  • proper structure of the wsgi.py file (Source)
  • Flask 1.0.2 Quickstart (Source)
  • Flask Deployment Options - mod_wsgi / Apache (Source)