Want some programming help? [close]

Setup Python WSGI apps on cPanel (Flask/Django)

Introduction

For a long time only PHP applications were well supported in cPanel, but now Python, Ruby, and Node.js apps can be deployed easily. In this guide, I will walk through the steps and configuration needed to run a plain WSGI app, a Flask app, and a Django app. In my cPanel version, it says cPanel 11 (86.0.13).

I am using cPanel provided by Interserver.net, a provider I have been using for years. They offer an unlimited hosting plan that lets you set up as many Python apps and domains as you want.

Steps

  • Go to cPanel home.
  • Under "Software" section, choose "Setup Python App"
  • Choose "Create application"
  • Fill out the options:
    • Python version - pick the latest Python 3 if you are unsure
    • Application root - Path relative to your home directory. You can provide full path like /home/nanodano/mypyapp but it will replace it with mypyapp
    • Application URL - Choose the domain name and URL to use. Leave blank or enter / to use the root of the domain. Note if you are using the default domain that points to public_html/ and you have an index.html file, that index file will take precedence for the root / URL.
    • Application startup file - e.g. app.py, the file that contains the WSGI application (Examples below). It will create a default file with some boilerplate to demonstrate it works so you do not have to provide a file before-hand.
    • Application Entry point - The name of the function/object that WSGI should load. e.g. app. Examples below, but it will automatically generate an example using your value.
    • Passenger log file - Path to store log entries
    • Environment variables - Any environment variables the app needs
  • Click "Create" to save and run the app

After creating the application, it will create a virtual environment for your application, and configure the web server to run your app. You can use the cPanel Terminal or SSH in to activate the virtual environment and pip install any modules you need to. For example, to install Flask:

pip install flask

Any time you make changes to the Python code, you need to restart the app from this panel.

Error messages will show up in your app directory in stderr.log.

At the top of the app detail page, it should tell you where the virtual environment is. The virtual environments for all apps by default are in your home directory like /home/nanodano/virtualenv/.

It will also add a .htaccess in the public_html directory that contains something like the following:

PassengerAppRoot "/home/nanodano/mypyapp"
PassengerBaseURI "/"
PassengerPython "/home/nanodano/virtualenv/mypyapp/3.7/bin/python3.7"
PassengerAppLogFile "/home/nanodano/passenger.log"

Make sure you don't remove that .htaccess file or the directory it creates. If you map your Python app to a URL like /myapp then it will create an empty directory with the same name in the public_html/ directory. For example: public_html/myapp. That is where the .htaccess file will be mapping that path to the Python app. If you mapped it to / then the .htaccess will be in public_html/.htaccess.

It will also create a file named passenger_wsgi.py that bootstraps our custom app. This is the code it generates automatically. You generally shouldn't have to modify it but it does give you the option.

import imp
import os
import sys


sys.path.insert(0, os.path.dirname(__file__))

wsgi = imp.load_source('wsgi', 'app.py')
application = wsgi.application

Also note that cached Python files can cause your changes not to take effect even after restarting your app. You may need to run a command like this to remove all the __pycache__, .pyc, and other files.

find . | grep -E "(__pycache__|\.pyc|\.pyo$)" | xargs rm -rf

Examples

You can run your own custom WSGI apps, or use a framework like Flask or Django since they are WSGI compliant. For this example I'll show the basic example as well as a Flask and Django example. These Python scripts correspond with the cPanel field for "Application startup file". I prefer to name them app.py.

Simple WSGI

This is the default WSGI boilerplate provided by my cPanel. There is nothing special about it except it does add its own directory to the path so you can include your own packages locally. To learn more about sys.path and how import works, check out my Python import, sys.path, and PYTHONPATH Tutorial.

# app.py
import os
import sys

sys.path.insert(0, os.path.dirname(__file__))

def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    message = 'It works!\n'
    version = 'Python v' + sys.version.split()[0] + '\n'
    response = '\n'.join([message, version])
    return [response.encode()]

Flask

Here is an example modified from above that will let you create a Flask application. We keep the sys.path modification to keep allowing us to import custom packages from the app directory if we want to later.

# app.py
import os
import sys
from flask import Flask

sys.path.insert(0, os.path.dirname(__file__))

app = Flask(__name__)

@app.route('/')
def index():
    return 'hi'

Django

You can setup Django in a similar fashion. You just need to find and configure the wsgi.py file and the name of the object in that file. If you use Django's startproject command, the entry point will be named application. Take the following example:

# Assuming you have activated the virtual environment. E.g.:
source ~/virtualenv/pyapp/3.7/bin/activate

# and installed django
pip install django

# Create a new Django project
django-admin startproject myproject

Then the "Application Startup File" will be myproject/myproject/wsgi.py.

The "Application Entry Point" will be application.

You will also need to update your ALLOWED_HOSTS entry to include the ip/domain name you are using to access the site.

If you get an error about SQLite requiring a newer version than available, you can work around this by commenting out the DATABASE object in the settings file or changing it to a database to another type.

If you get an error about not finding the settings file, you may need to alter the import path. For example, when I create the Python app in my cPanel dashboard, it auto-generates a file named passenger_wsgi.py that loads our WSGI application. The default file looks like this in my example:

import imp
import os
import sys


sys.path.insert(0, os.path.dirname(__file__))

wsgi = imp.load_source('wsgi', 'myproject/myproject/wsgi.py')
application = wsgi.application

NOTE: This passenger_wsgi.py file may be regenerated by the system if you restart the Python app. For this reason, it is better not to modify it, and modify your Django source code to include any extra sys.path modification so you changes will persist instead of doing it in the passenger_wsgi.py file which will get overwritten.

You can modify you Django's wsgi.py to include the following line:

sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..'))

This line will allow the proper importing of the settings file. This is what a Django's wsgi.py might look like after the additional line is added.

"""
WSGI config for myproject project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""
import sys
import os

# Add this line
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..'))

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

application = get_wsgi_application()

Be sure to remove any cached files like __pycache__ or .pyc in your app and restart the Python app in cPanel for changes to take effect.

To learn more about how the sys.path and imports work, check out my Python import, sys.path, and PYTHONPATH Tutorial.

Conclusion

After reading this guide, you should understand how to deploy any Python WSGI app including:

  • plain WSGI apps
  • Flask apps
  • Django apps

References

Advertisement